CommonMark.jl

A CommonMark-compliant parser for Julia.

Features

  • Spec compliant: Passes the CommonMark spec test suite
  • Multiple outputs: HTML, LaTeX, Typst, terminal (ANSI), Jupyter notebooks
  • Markdown roundtrip: Parse and re-emit normalized markdown
  • Modular parser: Enable/disable individual syntax rules
  • Extensions: Tables, footnotes, math, front matter, admonitions, and more

Installation

using Pkg
Pkg.add("CommonMark")

Quick Start

Create a parser, parse some markdown, and render it to HTML:

using CommonMark

parser = Parser()
ast = parser("Hello *world*")
html(ast)
"<p>Hello <em>world</em></p>\n"

The parser returns an abstract syntax tree (AST) that can be rendered to multiple output formats or inspected programmatically.

Parsing

The parser is callable on strings:

ast = parser("# Heading\n\nParagraph")
html(ast)
"<h1>Heading</h1>\n<p>Paragraph</p>\n"

For files, use open with the parser:

ast = open(parser, "document.md")

Output Formats

The same AST can be rendered to different formats. Each format has its own writer function that returns a string or writes to a file/IO.

ast = parser("# Title\n\n**Bold** and *italic*.")

HTML for web pages:

html(ast)
"<h1>Title</h1>\n<p><strong>Bold</strong> and <em>italic</em>.</p>\n"

LaTeX for documents and papers:

latex(ast)
"\\section{Title}\n\\textbf{Bold} and \\textit{italic}.\\par\n"

Markdown for normalization and roundtripping:

markdown(ast)
"# Title\n\n**Bold** and *italic*.\n"

Other formats include typst() for Typst documents, term() for terminal output with ANSI colors, notebook() for Jupyter notebooks, and json() for Pandoc AST JSON (enables export to docx, epub, rst, etc.). Use json(Dict, ast) to get the dict without JSON serialization.

All writer functions accept a filename or IO as the first argument:

html("output.html", ast)
term(stdout, ast)

Writers also accept an optional env dictionary for configuration. Front matter from the document is merged automatically:

env = Dict{String,Any}("title" => "My Page", "lang" => "en")
html(ast, env)

Transforms

Writers accept a transform keyword argument to intercept and modify nodes during rendering. Use transforms for URL rewriting, syntax highlighting, or wrapping output in templates:

# Rewrite .md links to .html for static sites
function rewrite_links(::MIME"text/html", link::CommonMark.Link, node, entering, writer)
    entering || return (node, entering)
    dest = replace(link.destination, ".md" => ".html")
    (CommonMark.Node(CommonMark.Link; dest, title=link.title), entering)
end
rewrite_links(mime, ::CommonMark.AbstractContainer, node, entering, writer) = (node, entering)

ast = parser("[Guide](guide.md)")
html(ast; transform = rewrite_links)
"<p><a href=\"guide.html\">Guide</a></p>\n"

See the Transforms page for full documentation.

Customization

The parser is modular. Each piece of syntax (headings, lists, emphasis, etc.) is handled by a rule that can be enabled or disabled independently.

By default, all standard CommonMark syntax is enabled. Extensions add syntax beyond the spec:

using CommonMark

parser = Parser()
enable!(parser, TableRule())
enable!(parser, FootnoteRule())
enable!(parser, MathRule())

ast = parser("""
| A | B |
|---|---|
| 1 | 2 |
""")
html(ast)
"<table><thead><tr><th align=\"left\">A</th><th align=\"left\">B</th></tr></thead><tbody><tr><td align=\"left\">1</td><td align=\"left\">2</td></tr></tbody></table>"

Default rules can be disabled if you want stricter or simpler parsing:

parser = Parser()
disable!(parser, SetextHeadingRule())  # Only allow # headings, not underlined

See Core Rules for the default syntax and Extensions for additional features like tables, math, admonitions, and a string macro for interpolation (cm"Hello $(name)!").