# \[ANN\] Cmarkit 0.4.0 - CommonMark parser and renderer for OCaml

**URL:** <https://discuss.ocaml.org/t/ann-cmarkit-0-4-0-commonmark-parser-and-renderer-for-ocaml/17435>\
**Category:** Community\
**Tags:** ocsf, announce\
**Created:** [October 31, 2025, 11:34pm UTC](https://discuss.ocaml.org/t/ann-cmarkit-0-4-0-commonmark-parser-and-renderer-for-ocaml/17435 "2025-10-31T23:34:48Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![dbuenzli](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/dbuenzli/32/18_2.png) [@dbuenzli](https://discuss.ocaml.org/u/dbuenzli)\
**Post date:** [October 31, 2025, 11:34pm UTC](https://discuss.ocaml.org/t/ann-cmarkit-0-4-0-commonmark-parser-and-renderer-for-ocaml/17435/1 "2025-10-31T23:34:48Z")

</div>

Hello,

It’s my pleasure to announce a new release of [cmarkit](https://erratique.ch/software/cmarkit), an ISC-licensed CommonMark parser and renderer for OCaml.

This release provides support for the latest version of the CommonMark specification, updated data for Unicode 17.0.0, a notable semantic change in the task item extension (thanks to @samoht) and a couple of bug fixes and improvements mostly in the CommonMark renderer.

All the details are in the [release notes](https://github.com/dbuenzli/cmarkit/blob/main/CHANGES.md#v040-2025-11-01-zagreb). Thanks to everyone who reported issues.

This release is brought to you by essential funding from the [OCaml software foundation](https://ocaml-sf.org/) and my [donors](https://github.com/sponsors/dbuenzli).

Homepage: [https://erratique.ch/software/cmarkit](https://erratique.ch/software/cmarkit)  
Docs: [https://erratique.ch/software/cmarkit/doc](https://erratique.ch/software/cmarkit/doc) (or `odig doc cmarkit`)  
Install: `opam install cmarkit` ([opam PR](https://github.com/ocaml/opam-repository/pull/28811))

* * *

P.S. I’m surprised by the number of users (or rather, dissatisfied users :–) of the CommonMark renderer. If you are using it don’t hesitate to tell how/why you are using it in this thread, just curious :–)

---

<div class="post-metadata">

**Author:** ![panglesd](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/panglesd/32/3931_2.png) [@panglesd](https://discuss.ocaml.org/u/panglesd)\
**Post date:** [November 3, 2025, 8:14pm UTC](https://discuss.ocaml.org/t/ann-cmarkit-0-4-0-commonmark-parser-and-renderer-for-ocaml/17435/2 "2025-11-03T20:14:29Z")

</div>

Thanks for the release!

> [@dbuenzli](#):
>
> P.S. I’m surprised by the number of users (or rather, dissatisfied users :–) of the CommonMark renderer. If you are using it don’t hesitate to tell how/why you are using it in this thread, just curious :–)

I’m a (happy so far) user of the Commonmark renderer, but I guess that don’t really count as it is for a forked version of cmarkit…I use it to allow the slipshow users to render the slipshow source (a heavily markdown-inspired syntax) into valid markdown, if they need.

---

<div class="post-metadata">

**Author:** ![dbuenzli](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/dbuenzli/32/18_2.png) [@dbuenzli](https://discuss.ocaml.org/u/dbuenzli)\
**Post date:** [November 3, 2025, 8:48pm UTC](https://discuss.ocaml.org/t/ann-cmarkit-0-4-0-commonmark-parser-and-renderer-for-ocaml/17435/3 "2025-11-03T20:48:07Z")

</div>

Thanks for the feedback. The reason I’m asking is because I have the feeling that you can often do without.

In this particular example:

> [@panglesd](#):
>
> I use it to allow the slipshow users to render the slipshow source (a heavily markdown-inspired syntax) into valid markdown, if they need.

Why don’t you parse with locations and extract your user’s own prose from the absolute location offsets found in the AST ? Source layout preservation [has its limitations](https://erratique.ch/software/cmarkit/doc/Cmarkit_commonmark/index.html#layout) which in turn leads to [known discrepancies](https://erratique.ch/software/cmarkit/doc/Cmarkit_commonmark/index.html#known_diffs) on rendering. As a user I think I’d rather see my own input so as not to be disoriented.

---

<div class="post-metadata">

**Author:** ![panglesd](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/panglesd/32/3931_2.png) [@panglesd](https://discuss.ocaml.org/u/panglesd)\
**Post date:** [November 3, 2025, 9:02pm UTC](https://discuss.ocaml.org/t/ann-cmarkit-0-4-0-commonmark-parser-and-renderer-for-ocaml/17435/4 "2025-11-03T21:02:06Z")

</div>

Yes, that would probably be a better way of doing it…

In this particular case, due its to low-priority I don’t think I’m going to change it. Unless if many people complain about the lack of source preservation, which I hope it won’t happen: this is meant as a compatibility hatch, and is really just a “side-feature”.

---

<div class="post-metadata">

**Author:** ![reynir](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/reynir/32/6496_2.png) [@reynir](https://discuss.ocaml.org/u/reynir)\
**Post date:** [November 6, 2025, 3:32pm UTC](https://discuss.ocaml.org/t/ann-cmarkit-0-4-0-commonmark-parser-and-renderer-for-ocaml/17435/5 "2025-11-06T15:32:09Z")

</div>

We use it for example in [builder-web](https://git.robur.coop/robur/builder-web) to render README files. We do some light processing to adjust heading levels so they fit in the document they are emitted into [Making sure you're not a bot!](https://git.robur.coop/robur/builder-web/src/commit/215f91f188a6fcb7672492334f823f18f11ce4fc/lib/utils.ml#L68-L91)

The fact that the renderer has the `?safe` argument allowed us to remove quite some code when migrating from omd.

---

<div class="post-metadata">

**Author:** ![dbuenzli](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/dbuenzli/32/18_2.png) [@dbuenzli](https://discuss.ocaml.org/u/dbuenzli)\
**Post date:** [November 6, 2025, 3:48pm UTC](https://discuss.ocaml.org/t/ann-cmarkit-0-4-0-commonmark-parser-and-renderer-for-ocaml/17435/6 "2025-11-06T15:48:42Z")

</div>

Cool, thanks for the links. But just to be clear you are not using the rendering to CommonMark or are you ? (but I do see a nice use of the [AST `Mapper`](https://erratique.ch/software/cmarkit/doc/Cmarkit/Mapper/index.html))

> [@reynir](#):
>
> The fact that the renderer has the `?safe` argument

Note, as [written in the documentation](https://erratique.ch/software/cmarkit/doc/Cmarkit_html/index.html#val-renderer) I don’t vouch the safety here :–) There are likely better tools to sanitize the HTML outputs. Maybe I should have called that `mostly_safe`, this was mostly cargo culted from [`cmark --safe`](https://github.com/commonmark/cmark/blob/fe8691301ebccd38995f1c4e3acdeacb5688aa6f/src/cmark.h#L613-L621).

---

<div class="post-metadata">

**Author:** ![reynir](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/reynir/32/6496_2.png) [@reynir](https://discuss.ocaml.org/u/reynir)\
**Post date:** [November 7, 2025, 8:19am UTC](https://discuss.ocaml.org/t/ann-cmarkit-0-4-0-commonmark-parser-and-renderer-for-ocaml/17435/7 "2025-11-07T08:19:07Z")

</div>

You’re right! I got confused by the terminology. Indeed, we use **html** renderer to render the parsed CommonMark documents.

> [@dbuenzli](#):
>
> Note, as [written in the documentation](https://erratique.ch/software/cmarkit/doc/Cmarkit_html/index.html#val-renderer) I don’t vouch the safety here :–)

Thanks for the heads up! I opened an issue in our repository for this. I don’t think it’s worse than our handwritten “sanitizer” we used for OMD (we wrote tests for what we sanitize, and Cmarkit passes them). So far the input markdown is all from repositories we control so it’s not been a high priority to be more precise :–)

---

<div class="post-metadata">

**Author:** ![dbuenzli](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/dbuenzli/32/18_2.png) [@dbuenzli](https://discuss.ocaml.org/u/dbuenzli)\
**Post date:** [November 8, 2025, 2:09am UTC](https://discuss.ocaml.org/t/ann-cmarkit-0-4-0-commonmark-parser-and-renderer-for-ocaml/17435/8 "2025-11-08T02:09:15Z")

</div>

> [@reynir](#):
>
> (we wrote tests for what we sanitize, and Cmarkit passes them).

If you write some that do not, I’m happy to update Cmarkit to handle them. Given that `safe` suppresses raw html I think the only remaining problem is links. Their unsafety is currently asserted with [this function](https://erratique.ch/software/cmarkit/doc/Cmarkit/Inline/Link/index.html#val-is_unsafe).

---

<div class="post-metadata">

**Author:** ![yawaramin](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/yawaramin/32/3384_2.png) [@yawaramin](https://discuss.ocaml.org/u/yawaramin)\
**Post date:** [December 22, 2025, 5:22am UTC](https://discuss.ocaml.org/t/ann-cmarkit-0-4-0-commonmark-parser-and-renderer-for-ocaml/17435/9 "2025-12-22T05:22:27Z")

</div>

Since Reynir mentioned they use the Commonmark-HTML renderer, I’ll just add that I’m doing the same thing in a side project of mine and am pretty happy with the result:

 ![Screenshot of the Zettelkit app showing a note rendered from Markdown source](https://us1.discourse-cdn.com/flex020/uploads/ocaml/original/2X/a/ab9088bb6a84705d60d7c1544ca0c0b6fabf8463.jpeg)

However, I do intend to write my own ‘safe’ renderer by traversing the parsed Markdown AST and keeping only the HTML markup I want to support. I was doing that with `omd` before I replaced it with `cmarkit` and started using the built-in renderer intending to make some progress and revisit it later.

---

<div class="post-metadata">

**Author:** ![dbuenzli](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/dbuenzli/32/18_2.png) [@dbuenzli](https://discuss.ocaml.org/u/dbuenzli)\
**Post date:** [December 22, 2025, 10:12am UTC](https://discuss.ocaml.org/t/ann-cmarkit-0-4-0-commonmark-parser-and-renderer-for-ocaml/17435/10 "2025-12-22T10:12:23Z")

</div>

> [@yawaramin](#):
>
> However, I do intend to write my own ‘safe’ renderer by traversing the parsed Markdown AST and keeping only the HTML markup I want to support.

Note that you can also selectively override the provided HTML renderer. There is an [example](https://erratique.ch/software/cmarkit/doc/Cmarkit_renderer/index.html#example) in the docs.
