commonmark, markdown-it, remark, and showdown are JavaScript libraries used to parse Markdown text and convert it into other formats, primarily HTML. showdown is one of the oldest, focusing on ease of use with a regex-based parser. commonmark is the strict reference implementation of the CommonMark spec, prioritizing spec compliance over extensibility. markdown-it is a high-performance parser that uses a token-based approach, offering a rich plugin ecosystem for rendering HTML. remark is part of the unified ecosystem, using an Abstract Syntax Tree (AST) to transform Markdown, making it ideal for complex transformations beyond simple HTML rendering.
When building applications that handle user-generated content, documentation, or static sites, choosing the right Markdown parser is critical. commonmark, markdown-it, remark, and showdown all solve the same core problem but take vastly different architectural approaches. Let's compare how they handle parsing, extensibility, and output generation.
The fundamental difference lies in how these libraries process text.
showdown uses a regex-based approach.
// showdown: Regex-based conversion
import showdown from 'showdown';
const converter = new showdown.Converter();
const html = converter.makeHtml('# Hello World');
// Output: <h1>Hello World</h1>
commonmark is a strict reference implementation.
// commonmark: Spec-compliant parsing
import commonmark from 'commonmark';
const reader = new commonmark.Parser();
const writer = new commonmark.HtmlRenderer();
const parsed = reader.parse('# Hello World');
const html = writer.render(parsed);
// Output: <h1>Hello World</h1>
markdown-it uses a token-based streaming parser.
// markdown-it: Token-based parsing
import markdownit from 'markdown-it';
const md = markdownit();
const html = md.render('# Hello World');
// Output: <h1>Hello World</h1>
remark uses an Abstract Syntax Tree (AST).
// remark: AST-based processing
import { remark } from 'remark';
import remarkHtml from 'remark-html';
const file = await remark()
.use(remarkHtml)
.process('# Hello World');
const html = String(file);
// Output: <h1>Hello World</h1>
Real-world apps often need custom syntax (e.g., alerts, emojis, or video embeds).
showdown supports extensions via a callback system.
// showdown: Custom extension
showdown.extension('headerId', function () {
return [{
type: 'output',
regex: /<h(\d)>(.*?)<\/h\1>/g,
replace: (match, level, content) => `<h${level} id="${content}">${content}</h${level}>`
}];
});
commonmark does not support plugins natively.
// commonmark: No native plugin support
// Custom logic requires manual AST traversal after parsing
const reader = new commonmark.Parser();
const parsed = reader.parse('# Hello');
// Manually walk 'parsed' tree to modify nodes before rendering
markdown-it has a rich plugin ecosystem.
// markdown-it: Plugin usage
import markdownit from 'markdown-it';
import emoji from 'markdown-it-emoji';
const md = markdownit().use(emoji);
const html = md.render(':smile:');
// Output: <p>😄</p>
remark relies on the unified plugin ecosystem.
// remark: Plugin usage
import { remark } from 'remark';
import remarkGfm from 'remark-gfm';
const file = await remark()
.use(remarkGfm) // Enables tables, strikethrough, etc.
.process('# Hello~world~');
console.log(String(file));
If you need to analyze or modify the document structure (e.g., extracting headings, checking links), the AST approach shines.
showdown does not expose an AST.
// showdown: No AST access
const html = converter.makeHtml('# Title');
// To modify, you must parse 'html' string with DOM tools
commonmark exposes a node tree but it is less flexible.
// commonmark: Basic tree walking
const walker = parsed.walker();
let event;
while ((event = walker.next())) {
const node = event.node;
if (node.type === 'heading') { /* ... */ }
}
markdown-it exposes tokens, not a full AST.
// markdown-it: Token inspection
const tokens = md.parse('# Title');
tokens.forEach(token => {
if (token.type === 'heading_open') { /* ... */ }
});
remark is built for AST manipulation.
// remark: AST transformation plugin
import { visit } from 'unist-util-visit';
const extractHeadings = () => (tree) => {
const headings = [];
visit(tree, 'heading', (node) => headings.push(node));
console.log(headings);
};
await remark().use(extractHeadings).process('# Title');
Performance varies based on input size and feature set.
showdown is lightweight but can slow down on large documents.
// showdown: Simple setup, potential perf cost on large text
const converter = new showdown.Converter();
const html = converter.makeHtml(largeMarkdownString);
commonmark is optimized for spec compliance, not speed.
// commonmark: Stable performance
const parsed = reader.parse(largeMarkdownString);
const html = writer.render(parsed);
markdown-it is highly optimized for speed.
// markdown-it: High performance rendering
const html = md.render(largeMarkdownString);
remark has higher overhead due to AST construction.
// remark: Build-time processing
const file = await remark().use(remarkHtml).process(largeMarkdownString);
Markdown parsers can introduce XSS vulnerabilities if they allow raw HTML.
showdown allows raw HTML by default.
sanitize: true to prevent script injection.// showdown: Security config
const converter = new showdown.Converter({ sanitize: true });
commonmark blocks raw HTML by default.
// commonmark: Safe by default
// Raw HTML tags in input are escaped automatically
markdown-it allows raw HTML by default.
markdown-it-sanitizer or external tools.// markdown-it: Manual sanitization
import sanitize from 'markdown-it-sanitizer';
const md = markdownit().use(sanitize);
remark does not sanitize by default.
rehype-sanitize in the pipeline for safety.// remark: Sanitization plugin
import rehypeSanitize from 'rehype-sanitize';
import remarkRehype from 'remark-rehype';
await remark()
.use(remarkRehype)
.use(rehypeSanitize)
.process(markdownContent);
| Feature | showdown | commonmark | markdown-it | remark |
|---|---|---|---|---|
| Architecture | Regex | Spec Reference | Token-based | AST (unified) |
| Extensibility | Extensions (Regex) | Low (Manual) | High (Plugins) | Very High (Plugins) |
| Output | HTML String | HTML String | HTML String | Any (via plugins) |
| Security | Configurable | Safe Default | Configurable | Manual (via plugins) |
| Best Use Case | Simple Apps | Spec Compliance | Content Sites | Build Tools / Transforms |
showdown is the old reliable 🛠️—easy to drop in for simple HTML conversion, but be careful with security settings. Great for legacy apps or quick prototypes.
commonmark is the strict librarian 📚—ensures everyone sees the exact same output according to the spec. Use when consistency across different systems is non-negotiable.
markdown-it is the speedster 🏎️—perfect for rendering content to HTML quickly with plenty of plugin options. Ideal for blogs, documentation, and forums.
remark is the transformer 🤖—best for build pipelines where you need to analyze, modify, or convert Markdown into various formats. Essential for static site generators and complex tooling.
Final Thought: If you just need HTML, markdown-it is usually the sweet spot. If you are building a tool that processes Markdown files (like a linter or generator), remark is the industry standard. Avoid showdown for new security-sensitive projects unless you strictly configure sanitization.
Choose commonmark if strict adherence to the CommonMark specification is your top priority and you need a predictable, stable parser without extra features. It is best suited for scenarios where spec compliance matters more than custom extensions or performance tweaks, such as documentation systems that must render identically across platforms.
Choose markdown-it if you need a fast, flexible parser for rendering Markdown to HTML with a wide range of plugins. It is ideal for content management systems, blogs, or forums where you need to support custom syntax (like emojis or containers) without sacrificing speed or stability.
Choose remark if you need to transform Markdown into other formats (like HTML, PDF, or even other Markdown variants) using a robust plugin ecosystem based on an Abstract Syntax Tree. It is the best choice for build tools, static site generators, or complex document processing pipelines where you need to inspect or modify the document structure.
Choose showdown if you need a simple, battle-tested library for basic Markdown-to-HTML conversion with minimal setup. It is suitable for legacy projects or simple applications where advanced AST manipulation or strict spec compliance is not required, and you prefer a straightforward API.
CommonMark is a rationalized version of Markdown syntax, with a spec and BSD-licensed reference implementations in C and JavaScript.
For more information, see http://commonmark.org.
This repository contains the JavaScript reference implementation. It provides a library with functions for parsing CommonMark documents to an abstract syntax tree (AST), manipulating the AST, and rendering the document to HTML or to an XML representation of the AST.
To play with this library without installing it, see the live dingus at http://try.commonmark.org/.
You can install the library using npm:
npm install commonmark
This package includes the commonmark library and a
command-line executable, commonmark.
For client-side use, you can use one of the single-file
distributions provided in the dist/ subdirectory
of the node installation (node_modules/commonmark/dist/).
Use either commonmark.js (readable source) or
commonmark.min.js (minimized source).
Alternatively, bower install commonmark will install
the needed distribution files in
bower_components/commonmark/dist.
You can also use the version hosted by unpkg: for example, https://unpkg.com/commonmark@0.29.3/dist/commonmark.js for the unminimized version 0.29.3.
Make sure to fetch dependencies with:
npm install
To run tests for the JavaScript library:
npm test
(Running the tests will also rebuild distribution files in
dist/.)
To run benchmarks against some other JavaScript converters:
make bench
To start an interactive dingus that you can use to try out the library:
make dingus
Instead of converting Markdown directly to HTML, as most converters
do, commonmark.js parses Markdown to an AST (abstract syntax tree),
and then renders this AST as HTML. This opens up the possibility of
manipulating the AST between parsing and rendering. For example, one
could transform emphasis into ALL CAPS.
Here's a basic usage example:
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 constructors for Parser and HtmlRenderer take an optional
options parameter:
var reader = new commonmark.Parser({smart: true});
var writer = new commonmark.HtmlRenderer({sourcepos: true});
Parser currently supports the following:
smart: if true, straight quotes will be made curly, -- will
be changed to an en dash, --- will be changed to an em dash, and
... will be changed to ellipses.Both HtmlRenderer and XmlRenderer (see below) support these options:
sourcepos: if true, source position information for block-level
elements will be rendered in the data-sourcepos attribute (for
HTML) or the sourcepos attribute (for XML).safe: if true, raw HTML will not be passed through to HTML
output (it will be replaced by comments), and potentially unsafe
URLs in links and images (those beginning with javascript:,
vbscript:, file:, and with a few exceptions data:) will
be replaced with empty strings.softbreak: specify raw string to be used for a softbreak.esc: specify a function to be used to escape strings. Its
argument is the string.For example, to make soft breaks render as hard breaks in HTML:
var writer = new commonmark.HtmlRenderer({softbreak: "<br />"});
To make them render as spaces:
var writer = new commonmark.HtmlRenderer({softbreak: " "});
XmlRenderer serves as an alternative to HtmlRenderer and
will produce an XML representation of the AST:
var writer = new commonmark.XmlRenderer({sourcepos: true});
The parser returns a Node. The following public properties are defined (those marked "read-only" have only a getter, not a setter):
type (read-only): a String, one of
text, softbreak, linebreak, emph, strong,
html_inline, link, image, code, document, paragraph,
block_quote, item, list, heading, code_block,
html_block, thematic_break.firstChild (read-only): a Node or null.lastChild (read-only): a Node or null.next (read-only): a Node or null.prev (read-only): a Node or null.parent (read-only): a Node or null.sourcepos (read-only): an Array with the following form:
[[startline, startcolumn], [endline, endcolumn]].isContainer (read-only): true if the Node can contain other
Nodes as children.literal: the literal String content of the node or null.destination: link or image destination (String) or null.title: link or image title (String) or null.info: fenced code block info string (String) or null.level: heading level (Number).listType: a String, either bullet or ordered.listTight: true if list is tight.listStart: a Number, the starting number of an ordered list.listDelimiter: a String, either ) or . for an ordered list.onEnter, onExit: Strings, used only for custom_block or
custom_inline.Nodes have the following public methods:
appendChild(child): Append a Node child to the end of the
Node's children.prependChild(child): Prepend a Node child to the
beginning of the Node's children.unlink(): Remove the Node from the tree, severing its links
with siblings and parents, and closing up gaps as needed.insertAfter(sibling): Insert a Node sibling after the Node.insertBefore(sibling): Insert a Node sibling before the Node.walker(): Returns a NodeWalker that can be used to iterate through
the Node tree rooted in the Node.The NodeWalker returned by walker() has two methods:
next(): Returns an object with properties entering (a boolean,
which is true when we enter a Node from a parent or sibling, and
false when we reenter it from a child). Returns null when
we have finished walking the tree.resumeAt(node, entering): Resets the iterator to resume at the
specified node and setting for entering. (Normally this isn't
needed unless you do destructive updates to the Node tree.)Here is an example of the use of a NodeWalker to iterate through
the tree, making transformations. This simple example converts
the contents of all text nodes to ALL CAPS:
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();
}
}
This more complex example converts emphasis to ALL CAPS:
var walker = parsed.walker();
var event, node;
var inEmph = false;
while ((event = walker.next())) {
node = event.node;
if (node.type === 'emph') {
if (event.entering) {
inEmph = true;
} else {
inEmph = false;
// add Emph node's children as siblings
while (node.firstChild) {
node.insertBefore(node.firstChild);
}
// remove the empty Emph node
node.unlink()
}
} else if (inEmph && node.type === 'text') {
node.literal = node.literal.toUpperCase();
}
}
Exercises for the reader: write a transform to
html_inline and html_block nodes).HtmlBlock
containing the highlighted code.The command line executable parses CommonMark input from the specified files, or from stdin if no files are specified, and renders the result to stdout as HTML. If multiple input files are specified, their contents are concatenated before parsing, with newlines between them.
commonmark inputfile.md > outputfile.html
commonmark intro.md chapter1.md chapter2.md > book.html
Use commonmark --help to get a summary of options.
The library does not attempt to sanitize link attributes or
raw HTML. If you use this library in applications that accept
untrusted user input, you should either enable the safe option
(see above) or run the output through an HTML sanitizer to protect against
XSS attacks.
Performance is excellent, roughly on par with marked. On a benchmark
converting an 11 MB Markdown file built by concatenating the Markdown
sources of all localizations of the first edition of
Pro Git by Scott
Chacon, the command-line tool, commonmark is just a bit slower than
the C program discount, roughly ten times faster than PHP Markdown,
a hundred times faster than Python Markdown, and more than
a thousand times faster than Markdown.pl.
Here are some focused benchmarks of four JavaScript libraries (using versions available on 24 Jan 2015). They test performance on different kinds of Markdown texts. (Most of these samples are taken from the markdown-it repository.) Results show a ratio of ops/second (higher is better) against showdown (which is usually the slowest implementation). Versions: showdown 1.3.0, marked 0.3.5, commonmark.js 0.22.1, markdown-it 5.0.2, node 5.3.0. Hardware: 1.6GHz Intel Core i5, Mac OSX.
| Sample | showdown | commonmark | marked | markdown-it |
|---|---|---|---|---|
| README.md | 1 | 3.6 | 3.1 | 3.9 |
| block-bq-flat.md | 1 | 4.8 | 4.9 | 4.9 |
| block-bq-nested.md | 1 | 11.9 | 6.8 | 10.7 |
| block-code.md | 1 | 4.7 | 12.1 | 23.0 |
| block-fences.md | 1 | 6.2 | 21.2 | 19.1 |
| block-heading.md | 1 | 5.0 | 4.8 | 6.5 |
| block-hr.md | 1 | 3.5 | 3.3 | 3.5 |
| block-html.md | 1 | 2.1 | 0.9 | 3.8 |
| block-lheading.md | 1 | 5.1 | 4.9 | 3.9 |
| block-list-flat.md | 1 | 4.7 | 4.4 | 7.4 |
| block-list-nested.md | 1 | 9.5 | 7.8 | 17.6 |
| block-ref-flat.md | 1 | 0.8 | 0.5 | 0.6 |
| block-ref-nested.md | 1 | 0.7 | 0.6 | 0.9 |
| inline-autolink.md | 1 | 2.3 | 3.4 | 2.5 |
| inline-backticks.md | 1 | 7.6 | 5.3 | 8.2 |
| inline-em-flat.md | 1 | 1.5 | 1.1 | 1.6 |
| inline-em-nested.md | 1 | 1.8 | 1.3 | 1.7 |
| inline-em-worst.md | 1 | 2.4 | 1.5 | 2.5 |
| inline-entity.md | 1 | 2.0 | 3.8 | 2.7 |
| inline-escape.md | 1 | 2.2 | 1.4 | 5.0 |
| inline-html.md | 1 | 2.9 | 3.7 | 3.3 |
| inline-links-flat.md | 1 | 2.7 | 2.7 | 2.2 |
| inline-links-nested.md | 1 | 1.4 | 0.5 | 0.5 |
| inline-newlines.md | 1 | 2.3 | 2.0 | 3.5 |
| lorem1.md | 1 | 6.0 | 2.9 | 3.3 |
| rawtabs.md | 1 | 4.6 | 3.9 | 6.7 |
To generate this table:
make bench-detailed
John MacFarlane wrote the first version of the JavaScript implementation. The block parsing algorithm was worked out together with David Greenspan. Kārlis Gaņģis helped work out a better parsing algorithm for links and emphasis, eliminating several worst-case performance issues. Vitaly Puzrin has offered much good advice about optimization and other issues.