# commonmark.js: a CommonMark parser you can edit between parse and render

> The commonmark/commonmark.js repository holds the JavaScript reference implementation of CommonMark, which parses Markdown to an AST rather than converting straight to HTML. The gap between the two stages is the feature, since you can rewrite the tree before it is rendered, and the package also ships a command line executable.

**commonmark/commonmark.js** — CommonMark parser and renderer in JavaScript

- Repository: https://github.com/commonmark/commonmark.js
- Stars: 1,571 · Forks: 232
- Language: JavaScript
- License: NOASSERTION
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/commonmark-commonmark-js

## A reference implementation, not a converter

CommonMark is a rationalized version of Markdown syntax, with a specification and BSD-licensed reference implementations in C and JavaScript. This repository is the JavaScript one, and its author is John MacFarlane, who also wrote the specification. The package description calls it a strongly specified, highly compatible variant of Markdown, and the project site is commonmark.org with the specification at spec.commonmark.org.

What the library provides is a set of functions for parsing CommonMark documents into an abstract syntax tree, manipulating that tree, and rendering the document to HTML or to an XML representation of the AST. The distinction from most converters is the reason to choose it. A typical converter takes a string and returns a string, and the intermediate representation is an implementation detail you cannot touch. Here the tree is public, with named node types, documented properties, and methods for moving nodes around.

The scope is deliberately narrow. This is a Markdown parser, not a site generator, and there is no plugin system for extending the syntax beyond what the specification defines. If you need to add a new block type, the answer is not in this library. What it does give you is a well-defined tree and the confidence that the tree matches the specification, because the same project maintains the spec and the reference implementation.

The licence is BSD-2-Clause in the package manifest, though the repository metadata reports a custom licence that GitHub cannot classify, so the LICENSE file is the text that governs.

## The parse, transform, render gap

The usage section puts the design in one sentence. Instead of converting Markdown directly to HTML, as most converters do, commonmark.js parses Markdown to an AST and then renders that AST as HTML, and this opens the possibility of manipulating the AST between parsing and rendering. The example the documentation gives is deliberately small, transforming emphasis into ALL CAPS.

``` js
var reader = new commonmark.Parser();
var writer = new commonmark.HtmlRenderer();
var parsed = reader.parse("Hello *world*"); // parsed is a 'Node' tree
// transform parsed if you like...
var result = writer.render(parsed); // result is a String
```

The three-step shape is what makes the library composable. You can parse once and render many ways, so a document that must be emitted as HTML for a page and as XML for a feed goes through the parser a single time. You can also render the same tree with different writers, which is how a pipeline can produce a sanitised HTML version and a full-fidelity one from a single parse.

The node model is documented in detail. A node has a read-only type drawn from a fixed list including text, softbreak, linebreak, emph, strong, html_inline, link, image, code, document, paragraph, block_quote, item, list, heading, code_block, html_block, and thematic_break, along with firstChild, lastChild, next, prev, parent, sourcepos, and isContainer, and writable properties such as literal, destination, title, info, level, listType, listTight, listStart, and listDelimiter. The tree is mutable through appendChild, prependChild, unlink, insertAfter, insertBefore, and a walker method that returns a NodeWalker.

## Walking the tree and making destructive edits

The walker is the tool you reach for when you want to visit every node, and its contract is specific enough to be worth reading before you build on it. The next method returns an object with an entering property, which is true when entering a node from a parent or sibling and false when re-entering it from a child, and it returns null when the walk is finished. That pairing of entering and exiting is what lets a transformation distinguish a node it is entering from a node it is leaving, which is exactly the distinction you need to know whether a text node is inside a strong emphasis or inside a link.

The documented example converts the contents of all text nodes to uppercase, and the walker pattern is the standard one for the job:

``` js
var walker = parsed.walker();
var event, node;

while ((event = walker.next())) {
  node = event.node;
  if (event.entering && node.type === 'text') {
    node.literal = node.literal.toUpperCase();
  }
}
```

The second walker method is the one that catches people out. resumeAt(node, entering) resets the iterator so the walk continues from a specific node with a specific entering state, and the documentation notes it is normally not needed unless you are doing destructive updates to the tree. That caveat is the important part. If you call unlink on a node while a walker is running, the walker's internal position can end up pointing at a node that is no longer in the tree, and resumeAt is how you recover from that. Editing a tree you are currently traversing is the classic way to corrupt a document, and this API gives you the repair tool rather than leaving you to rebuild the traversal by hand.

## Options: smart punctuation, source positions, and the safe mode

The constructors for Parser and HtmlRenderer take an optional options parameter, and the same documentation lists what each option does.

``` js
var reader = new commonmark.Parser({smart: true});
var writer = new commonmark.HtmlRenderer({sourcepos: true});
```

The smart option on the parser turns straight quotes into curly quotes, changes a double hyphen into an en dash, a triple hyphen into an em dash, and three dots into ellipses. It is the closest thing to an opinionated typographic layer, and it is off unless you ask for it.

Both renderers support sourcepos, which puts the position of block level elements in the data-sourcepos attribute for HTML and the sourcepos attribute for XML. The value is an array of the form [[startline, startcolumn], [endline, endcolumn]], which is exactly what you need to map an error message in rendered output back to a line in the source document, or to drive a table of contents.

The safe option is the one with a real security consequence. When it is true, raw HTML is not passed through to the output and is replaced by comments, and potentially unsafe URLs in links and images are replaced with empty strings. Those URLs are the ones beginning with javascript:, vbscript:, and file:, and with a few exceptions data:. Note the shape of that guarantee, since it is narrower than the name suggests. It covers raw HTML passthrough and specific URL schemes. It does not make the output safe from any other cause, such as an image URL pointing at an internal host that fetches and leaks, so a service using safe mode still needs its own URL filtering if it renders untrusted Markdown to other users.

Two more options handle output shape. The softbreak option takes a raw string used for a softbreak, so setting it to a br tag renders soft breaks as hard breaks, and setting it to a space renders them as spaces. The esc option takes a function used to escape strings, with the string as its argument, which lets you swap the escaping strategy for a different output target.

## XML output, the CLI, and the browser distribution

XmlRenderer is the alternative to HtmlRenderer, and it produces an XML representation of the same AST, so the writer is swapped without the parse changing:

``` js
var writer = new commonmark.XmlRenderer({sourcepos: true});
```

That is the practical value of the AST design. A tool that needs to post-process a document, for example to rewrite links, can work on the tree and then emit whatever format the consumer needs, HTML or XML, without maintaining two parsers.

The npm package installs with npm install commonmark, and it includes both the library and a command-line executable named commonmark, wired in the manifest as a bin entry. The package is a CommonJS type package whose main entry points at the built dist/commonmark.js, with a module field pointing at lib/index.js, and an exports map that sends require calls to the dist build and default imports to the lib source. That split explains a practical wrinkle: which file you get depends on how you load it, and the built dist file is the one a bundler or a plain node require sees.

For browser use, the README points at single-file distributions in the dist/ subdirectory of the installed package, with a readable commonmark.js and a minimised commonmark.min.js, and mentions bower install commonmark and the unpkg CDN as alternatives, giving a specific versioned URL for the unminimised build. A live dingus is hosted at try.commonmark.org for trying the library without installing it.

## Building, testing against the spec, and the release state

The development instructions are short. npm install fetches the dependencies, npm test runs the JavaScript test suite, and the documentation notes that running the tests also rebuilds the distribution files in dist/. The package manifest makes that explicit, with a pretest script that runs the build and a prepublish script that does the same, so the published artefact is always built from source.

The build is a rollup configuration, with rollup-plugin-esbuild-minify for the minified bundle, and the scripts are minimal: build, lint, test, prepublish, pretest. Linting is eslint over the whole project.

The test suite has a distinguishing feature, which is that the specification file is the fixture. The Makefile points at test/spec.txt, derives the spec version from a version line inside it, and provides an update-spec target that curls the current spec.txt from the jgm/CommonMark repository. The test target depends on that file and then calls npm test. In other words, the tests are the spec, and updating the spec pulls in the new test cases. That is the mechanism by which a reference implementation stays conformant, and it is why the project tracks the specification rather than treating it as a document written once.

There are also make targets for benchmarking against other JavaScript converters, which is where markdown-it, marked, and showdown appear as dev dependencies, and a dingus target that builds the interactive playground from the dingus/ directory.

On releases, the version is 0.31.2, published on 2024-09-19, with 0.31.1 in July 2024 and 0.31.0 in January 2024, and the last push to the repository was on 2026-10-13. The published package and the working tree have drifted apart in time, and the unpkg example in the README points at 0.29.3, an older line.

## Against a direct converter, and against markdown-it

The realistic alternatives are the direct converters, and the benchmark target names several of them as markdown-it, marked, and showdown. The difference in approach is the presence of the tree. A direct converter produces a string, and if you need to inspect or change the document structure you are limited to rewriting the Markdown source or post-processing the HTML with a parser of a different flavour. commonmark.js gives you the structure, and the cost is that you write three lines of code instead of one and hold a tree in memory.

That cost matters for a small job. If your requirement is render a string of Markdown to HTML for a comment box, the AST is overhead. The justification for paying it is a requirement that a converter cannot meet: you need to rewrite the document, count headings, extract links, sanitise only part of the output, or emit two formats from one parse. Once you have one of those requirements, the tree stops being overhead and starts being the reason the library exists.

The comparison with markdown-it is worth a separate note. The benchmark target exists to compare them, and the documentation notes the benchmark can be run over a single input file or in detail across every sample in bench/samples. The difference here is that commonmark.js is the reference implementation for the specification, and its test suite is the specification file, which is a different kind of assurance. A reference implementation is a strong choice when conformance is the requirement, and a less interesting one when throughput is.

The XML renderer also has no equivalent in most converters, and that is the feature most likely to justify the library on its own for a document pipeline that has to produce structured output rather than markup.

## Conclusion

Adopt commonmark.js if you need CommonMark conformance specifically, or if you need to transform the document tree between parsing and rendering. Do not adopt it for a small project that only needs Markdown to HTML, since the AST machinery and the older package layout cost more than a direct converter. Verify first whether the published 0.31.2 build matches your module system, because the package points CommonJS and ES module consumers at different entry points.

## FAQ

### How do I install commonmark.js and use it?

Install with npm install commonmark, then create a Parser and an HtmlRenderer, call reader.parse to get a Node tree, transform it if you want, and pass it to writer.render to get a string. The constructors accept an optional options object, for example new commonmark.Parser({smart: true}).

### What does the safe option do in commonmark.js?

When safe is true, raw HTML is not passed through to the HTML output and is replaced by comments, and potentially unsafe URLs in links and images are replaced with empty strings. Those are URLs beginning with javascript:, vbscript:, and file:, and with a few exceptions data:.

### How do I walk and modify the AST in commonmark.js?

Call walker() on the parsed node to get a NodeWalker, then loop on walker.next(), which returns an object with an entering flag and returns null when the walk is done. Nodes expose appendChild, prependChild, unlink, insertAfter, insertBefore, and a writable literal property, and resumeAt can reset the iterator after destructive edits.

### Does commonmark.js ship a command line tool?

Yes. The npm package includes the library and a command-line executable named commonmark, and browser users can load the single-file builds in the dist/ directory of the installed package, either the readable commonmark.js or the minimised commonmark.min.js.

### How does the commonmark.js test suite stay in sync with the specification?

The Makefile treats test/spec.txt as the test fixture, derives the spec version from it, and provides an update-spec target that fetches the current spec.txt from the jgm/CommonMark repository. The test target depends on that file and runs npm test, which also rebuilds the distribution files.

## Sources

- [commonmark/commonmark.js on GitHub](https://github.com/commonmark/commonmark.js)
- [Issues](https://github.com/commonmark/commonmark.js/issues)
- [README](https://github.com/commonmark/commonmark.js/blob/master/README.md)
- [Releases](https://github.com/commonmark/commonmark.js/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/commonmark-commonmark-js
