Extensions

Extensions add syntax beyond the CommonMark specification. They must be explicitly enabled with enable!.

using CommonMark

Tables

Pipe-style tables from GitHub Flavored Markdown. Tables require a header row and a separator row that defines column alignment.

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

ast = parser("""
| Column One | Column Two | Column Three |
|:---------- | ---------- |:------------:|
| Row `1`    | Column `2` |              |
| *Row* 2    | **Row** 2  | Column 3     |
""")
html(ast)
"<table><thead><tr><th align=\"left\">Column One</th><th align=\"left\">Column Two</th><th align=\"center\">Column Three</th></tr></thead><tbody><tr><td align=\"left\">Row <code>1</code></td><td align=\"left\">Column <code>2</code></td><td align=\"center\"></td></tr><tr><td align=\"left\"><em>Row</em> 2</td><td align=\"left\"><strong>Row</strong> 2</td><td align=\"center\">Column 3</td></tr></tbody></table>"

Alignment is set with colons in the separator: :--- left, ---: right, :---: center. Cells can contain inline formatting. Escape literal pipes with backslashes.

Grid Tables

Pandoc-compatible grid tables for multi-line cells, colspan, rowspan, and block content within cells. More powerful than pipe tables but with heavier syntax.

parser = Parser()
enable!(parser, GridTableRule())

ast = parser("""
+-------+-------+-------+
| Head1 | Head2 | Head3 |
+=======+=======+=======+
| Body1 | Body2 | Body3 |
+-------+-------+-------+
| Row 2 | Row 2 | Row 2 |
+-------+-------+-------+
""")
html(ast)
"<table><thead><tr><th align=\"left\">\n<p>Head1</p>\n</th><th align=\"left\">\n<p>Head2</p>\n</th><th align=\"left\">\n<p>Head3</p>\n</th></tr></thead><tbody><tr><td align=\"left\">\n<p>Body1</p>\n</td><td align=\"left\">\n<p>Body2</p>\n</td><td align=\"left\">\n<p>Body3</p>\n</td></tr><tr><td align=\"left\">\n<p>Row 2</p>\n</td><td align=\"left\">\n<p>Row 2</p>\n</td><td align=\"left\">\n<p>Row 2</p>\n</td></tr></tbody></table>"

Multi-line Cells

Cells can span multiple lines and contain block content like paragraphs, lists, and code blocks.

ast = parser("""
+----------+----------+
| Line one | Single   |
| Line two |          |
+==========+==========+
| Body     | - Item 1 |
|          | - Item 2 |
+----------+----------+
""")
html(ast)
"<table><thead><tr><th align=\"left\">\n<p>Line one\nLine two</p>\n</th><th align=\"left\">\n<p>Single</p>\n</th></tr></thead><tbody><tr><td align=\"left\">\n<p>Body</p>\n</td><td align=\"left\">\n<ul>\n<li>Item 1</li>\n<li>Item 2</li>\n</ul>\n</td></tr></tbody></table>"

Colspan and Rowspan

Omitting + in a border merges cells horizontally (colspan). Partial borders between rows merge cells vertically (rowspan).

ast = parser("""
+----------+----------------------+
| Location | Temperature          |
|          +------+------+--------+
|          | min  | mean | max    |
+==========+======+======+========+
| Chicago  | -10  | 15   | 35     |
+----------+------+------+--------+
| Berlin   | -5   | 12   | 30     |
+----------+------+------+--------+
""")
html(ast)
"<table><thead><tr><th align=\"left\" rowspan=\"2\">\n<p>Location</p>\n</th><th align=\"left\" colspan=\"3\">\n<p>Temperature</p>\n</th></tr><tr><th align=\"left\">\n<p>min</p>\n</th><th align=\"left\">\n<p>mean</p>\n</th><th align=\"left\">\n<p>max</p>\n</th></tr></thead><tbody><tr><td align=\"left\">\n<p>Chicago</p>\n</td><td align=\"left\">\n<p>-10</p>\n</td><td align=\"left\">\n<p>15</p>\n</td><td align=\"left\">\n<p>35</p>\n</td></tr><tr><td align=\"left\">\n<p>Berlin</p>\n</td><td align=\"left\">\n<p>-5</p>\n</td><td align=\"left\">\n<p>12</p>\n</td><td align=\"left\">\n<p>30</p>\n</td></tr></tbody></table>"

Here "Location" spans two rows and "Temperature" spans three columns.

Enclose the last rows with = separators to create a <tfoot> section.

ast = parser("""
+-------+-------+
| Head1 | Head2 |
+=======+=======+
| Body1 | Body2 |
+=======+=======+
| Foot1 | Foot2 |
+=======+=======+
""")
html(ast)
"<table><thead><tr><th align=\"left\">\n<p>Head1</p>\n</th><th align=\"left\">\n<p>Head2</p>\n</th></tr></thead><tbody><tr><td align=\"left\">\n<p>Body1</p>\n</td><td align=\"left\">\n<p>Body2</p>\n</td></tr></tbody><tfoot><tr><td align=\"left\">\n<p>Foot1</p>\n</td><td align=\"left\">\n<p>Foot2</p>\n</td></tr></tfoot></table>"

Nested Tables

Cells can contain nested grid tables.

ast = parser("""
+-------------------------+---------------+
| Outer Left              | Outer Right   |
+=========================+===============+
| +------+------+         | Regular cell  |
| | A    | B    |         |               |
| +------+------+         |               |
| | C    | D    |         |               |
| +------+------+         |               |
+-------------------------+---------------+
""")
html(ast)
"<table><thead><tr><th align=\"left\">\n<p>Outer Left</p>\n</th><th align=\"left\">\n<p>Outer Right</p>\n</th></tr></thead><tbody><tr><td align=\"left\"><table><tbody><tr><td align=\"left\">\n<p>A</p>\n</td><td align=\"left\">\n<p>B</p>\n</td></tr><tr><td align=\"left\">\n<p>C</p>\n</td><td align=\"left\">\n<p>D</p>\n</td></tr></tbody></table></td><td align=\"left\">\n<p>Regular cell</p>\n</td></tr></tbody></table>"

Definition Lists

Pandoc-compatible definition lists. Terms are plain text lines followed by definitions marked with : and 1–3 spaces.

parser = Parser()
enable!(parser, DefinitionListRule())

ast = parser("""
Term
:   Definition of the term.
""")
html(ast)
"<dl>\n<dt>Term</dt>\n<dd>\nDefinition of the term.</dd>\n</dl>\n"

Multiple Definitions

A term can have several definitions.

ast = parser("""
Term
:   First definition.
:   Second definition.
""")
html(ast)
"<dl>\n<dt>Term</dt>\n<dd>\nFirst definition.</dd>\n<dd>\nSecond definition.</dd>\n</dl>\n"

Multiple Terms

Separate groups with blank lines. Adjacent groups merge into a single list.

ast = parser("""
Apple
:   A fruit.

Orange
:   Another fruit.
""")
html(ast)
"<dl>\n<dt>Apple</dt>\n<dd>\nA fruit.</dd>\n<dt>Orange</dt>\n<dd>\nAnother fruit.</dd>\n</dl>\n"

Loose Definitions

A blank line between the term and : wraps content in <p> tags.

ast = parser("""
Term

:   Loose definition.
""")
html(ast)
"<dl>\n<dt>Term</dt>\n<dd>\n<p>Loose definition.</p>\n</dd>\n</dl>\n"

Block Content

Definitions can contain multiple paragraphs, lists, and other block elements. Continuation lines are indented by 4 spaces.

ast = parser("""
Term
:   First paragraph.

    Second paragraph.

    - item one
    - item two
""")
html(ast)
"<dl>\n<dt>Term</dt>\n<dd>\n<p>First paragraph.</p>\n<p>Second paragraph.</p>\n<ul>\n<li>item one</li>\n<li>item two</li>\n</ul>\n</dd>\n</dl>\n"

Admonitions

Callout boxes for notes, warnings, tips, and other highlighted content. Common in technical documentation.

parser = Parser()
enable!(parser, AdmonitionRule())

ast = parser("""
!!! note "Custom Title"
    This is an admonition block.

!!! warning
    Title defaults to category name.
""")
html(ast)
"<div class=\"admonition note\"><p class=\"admonition-title\">Custom Title</p>\n<p>This is an admonition block.</p>\n</div><div class=\"admonition warning\"><p class=\"admonition-title\">Warning</p>\n<p>Title defaults to category name.</p>\n</div>"

The category (note, warning, tip, etc.) determines styling. An optional quoted string overrides the title. Content must be indented by 4 spaces.

Footnotes

Reference-style footnotes that collect at the end of the document. Useful for citations, asides, and additional context without interrupting flow.

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

ast = parser("""
Here is a footnote reference[^1].

[^1]: This is the footnote content.
""")
html(ast)
"<p>Here is a footnote reference<a href=\"#footnote-1\" class=\"footnote\">1</a>.</p>\n<div class=\"footnote\" id=\"footnote-1\"><p class=\"footnote-title\">1</p>\n<p>This is the footnote content.</p>\n</div>"

Footnote identifiers can be any word or number. Definitions can appear anywhere in the document and will be collected at the end.

Typography

Converts ASCII punctuation to proper typographic characters. Makes documents look more polished without requiring special input.

parser = Parser()
enable!(parser, TypographyRule())

ast = parser("\"Hello\" -- Pro tip... use 'single quotes' too --- or not.")
html(ast)
"<p>“Hello” – Pro tip… use ‘single quotes’ too — or not.</p>\n"

Conversions: straight quotes to curly quotes, ... to ellipsis, -- to en-dash, --- to em-dash. Disable specific conversions with keyword arguments:

enable!(parser, TypographyRule(double_quotes=false, dashes=false))

Math

LaTeX math expressions for technical and scientific documents.

Julia-style (double backticks)

Uses double backticks for inline math, matching Julia's docstring convention. Display math uses fenced code blocks with math as the language.

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

ast = parser("Inline ``E = mc^2`` math.")
html(ast)
"<p>Inline <span class=\"math tex\">\\(E = mc^2\\)</span> math.</p>\n"

Display math with fenced blocks:

ast = parser("""
```math
\\int_0^\\infty e^{-x^2} dx
```
""")
html(ast)
"<div class=\"display-math tex\">\\[\\int_0^\\infty e^{-x^2} dx\\]</div>"

Dollar-style

Traditional LaTeX syntax with single $ for inline and double $$ for display. More familiar to users coming from LaTeX or other markdown flavors.

parser = Parser()
enable!(parser, DollarMathRule())

ast = parser("Inline \$E = mc^2\$ math.")
html(ast)
"<p>Inline <span class=\"math tex\">\\(E = mc^2\\)</span> math.</p>\n"

Attributes

Attach IDs, classes, and arbitrary key-value pairs to elements. Useful for styling, linking, and integrating with JavaScript.

parser = Parser()
enable!(parser, AttributeRule())

ast = parser("""
{#my-id .highlight}
# Heading
""")
html(ast)
"<h1 class=\"highlight\" id=\"my-id\"><a href=\"#my-id\" class=\"anchor\"></a>Heading</h1>\n"

Block attributes go above the target element. Inline attributes go after:

ast = parser("*text*{.important}")
html(ast)
"<p><em class=\"important\">text</em></p>\n"

CSS shorthand: #foo expands to id="foo", .bar expands to class="bar".

Strikethrough

Marks deleted or outdated text. Renders as <del> in HTML.

parser = Parser()
enable!(parser, StrikethroughRule())

ast = parser("~~deleted text~~")
html(ast)
"<p><del>deleted text</del></p>\n"

Mark

Highlights important text. Renders as <mark> in HTML. Follows Pandoc's +mark extension syntax.

parser = Parser()
enable!(parser, MarkRule())

ast = parser("This is ==highlighted text==.")
html(ast)
"<p>This is <mark>highlighted text</mark>.</p>\n"

Uses double equals signs. Single equals are unaffected (for code examples like a = b).

Subscript

Chemical formulas, mathematical notation, and other subscripted text.

parser = Parser()
enable!(parser, SubscriptRule())

ast = parser("H~2~O")
html(ast)
"<p>H<sub>2</sub>O</p>\n"

Can be combined with StrikethroughRule since they use different tilde counts (single vs double).

Superscript

Exponents, ordinals, and other superscripted text.

parser = Parser()
enable!(parser, SuperscriptRule())

ast = parser("x^2^")
html(ast)
"<p>x<sup>2</sup></p>\n"

Task Lists

Interactive checklists from GitHub Flavored Markdown. Useful for todo lists and progress tracking.

parser = Parser()
enable!(parser, TaskListRule())

ast = parser("""
- [ ] Unchecked
- [x] Checked
""")
html(ast)
"<ul>\n<li><input type=\"checkbox\" disabled> Unchecked</li>\n<li><input type=\"checkbox\" disabled checked> Checked</li>\n</ul>\n"

GitHub Alerts

Styled callouts matching GitHub's markdown alerts. Similar to admonitions but with GitHub's specific syntax and categories.

parser = Parser()
enable!(parser, GitHubAlertRule())

ast = parser("""
> [!NOTE]
> Useful information.

> [!WARNING]
> Important warning.
""")
html(ast)
"<div class=\"github-alert note\"><p class=\"github-alert-title\">Note</p>\n<p>Useful information.</p>\n</div>\n<div class=\"github-alert warning\"><p class=\"github-alert-title\">Warning</p>\n<p>Important warning.</p>\n</div>\n"

Supported types: NOTE, TIP, IMPORTANT, WARNING, CAUTION.

Fenced Divs

Generic containers from Pandoc. Wrap arbitrary content in a div with classes and attributes. Useful for custom styling and semantic markup.

parser = Parser()
enable!(parser, FencedDivRule())

ast = parser("""
::: warning
This is a warning.
:::
""")
html(ast)
"<div class=\"warning\">\n<p>This is a warning.</p>\n</div>\n"

Divs can be nested by using more colons for outer fences.

Front Matter

Structured metadata at the start of a document. Commonly used for titles, authors, dates, and configuration in static site generators.

using YAML

parser = Parser()
enable!(parser, FrontMatterRule(yaml=YAML.load))

ast = parser("""
---
title: My Document
author: Jane Doe
---

Content here.
""")
frontmatter(ast)
Dict{String, Any} with 2 entries:
  "author" => "Jane Doe"
  "title"  => "My Document"

The frontmatter(ast) function extracts metadata as a dictionary. Returns an empty dict if no front matter is present.

Delimiters determine format: --- for YAML, +++ for TOML, ;;; for JSON. Pass the appropriate parser function for each format you want to support.

Citations

Academic-style citations with Pandoc syntax. Requires bibliography data in CSL-JSON format passed to the writer.

parser = Parser()
enable!(parser, CitationRule())

ast = parser("According to @doe2020, this is true.")

# Bibliography as CSL-JSON array
bib = [Dict(
    "id" => "doe2020",
    "author" => [Dict("family" => "Doe", "given" => "Jane")],
    "title" => "Example Article",
    "issued" => Dict("date-parts" => [[2020]])
)]
html(ast, Dict{String,Any}("references" => bib))
"<p>According to <span class=\"citation\"><a href=\"#ref-doe2020\">Doe 2020</a></span>, this is true.</p>\n"

Bracketed syntax groups multiple citations: [@doe2020; @smith2021]. Brackets render as parentheses in the output.

Auto Identifiers

Automatically generates IDs for headings based on their text. Enables linking directly to sections.

parser = Parser()
enable!(parser, AutoIdentifierRule())

ast = parser("# My Heading")
html(ast)
"<h1 id=\"my-heading\"><a href=\"#my-heading\" class=\"anchor\"></a>My Heading</h1>\n"

IDs are slugified: lowercased, spaces become hyphens, special characters removed. Duplicate headings get numeric suffixes.

Preserves reference-style link syntax in the AST instead of resolving it during parsing. Enables accurate markdown roundtripping and detection of undefined references.

parser = Parser()
enable!(parser, ReferenceLinkRule())

ast = parser("""
[full style][ref]
[collapsed style][]
[shortcut style]

[ref]: https://example.com
[collapsed style]: /url
[shortcut style]: /url
""")
markdown(ast)
"[full style][ref]\n[collapsed style][]\n[shortcut style]\n\n[ref]: https://example.com\n\n[collapsed style]: /url\n\n[shortcut style]: /url\n"

The three reference styles are preserved:

  • Full: [text][label] - explicit label
  • Collapsed: [text][] - label matches text
  • Shortcut: [text] - implicit label

Detecting Undefined References

When enabled, undefined references become UnresolvedReference nodes instead of literal text. This enables tools to find broken links:

ast = parser("[undefined link][missing]")
for (node, entering) in ast
    if entering && node.t isa CommonMark.UnresolvedReference
        ref = node.t
        println("Undefined: label='$(ref.label)', style=$(ref.style), image=$(ref.image)")
    end
end
Undefined: label='missing', style=full, image=false

Raw Content

Pass format-specific content through unchanged. Useful for embedding LaTeX commands, HTML widgets, or other content that shouldn't be processed.

parser = Parser()
enable!(parser, RawContentRule())

ast = parser("Inline: `\\textbf{bold}`{=latex}\n\n```{=latex}\n\\begin{center}\nCentered\n\\end{center}\n```")

Raw content only appears in its target format:

latex(ast)
"Inline: \\textbf{bold}\\par\n\\begin{center}\nCentered\n\\end{center}\n"
html(ast)  # LaTeX content omitted
"<p>Inline: </p>\n"

The format name (html, latex, typst) is specified in the attribute. The parser automatically determines inline vs block from context. Custom formats can be added by passing type mappings to RawContentRule.

Shortcodes

Lightweight macros that expand into content, inspired by Hugo and Quarto. Shortcodes use {{< name args >}} syntax by default. A standalone shortcode on its own line becomes a block-level node; otherwise it's inline.

parser = Parser()
enable!(parser, ShortcodeRule())

ast = parser("Text {{< ref page >}} here.")
html(ast)
"<p>Text {{< ref page >}} here.</p>\n"

Block shortcodes (standalone on a line):

ast = parser("{{< pagebreak >}}")
html(ast)
"{{< pagebreak >}}\n"

Unexpanded shortcodes pass through to output as-is, preserving the original syntax. This is useful when a downstream tool handles expansion.

Parse-Time Handlers

Register handlers on the rule to expand shortcodes during parsing. Handlers receive (name, args, kwargs, ctx::ShortcodeContext) and return a Node:

handlers = Dict{String,Function}(
    "greeting" => (name, args, kwargs, ctx) -> CommonMark.text("Hello, " * first(args) * "!"),
)
parser = Parser()
enable!(parser, ShortcodeRule(handlers=handlers))

ast = parser("Say {{< greeting world >}}.")
html(ast)
"<p>Say Hello, world!.</p>\n"

The ShortcodeContext provides source (file path), sourcepos, and document meta. Unmatched shortcodes remain in the AST for write-time handling.

Write-Time Transforms

For format-specific expansion, use the Transforms system to dispatch on Shortcode or ShortcodeBlock:

function xform(::MIME"text/html", sc::CommonMark.Shortcode, node, entering, writer)
    if sc.name == "icon"
        (CommonMark.Node(CommonMark.HtmlInline, "<i class=\"icon-" * first(sc.args) * "\"></i>"), entering)
    else
        (node, entering)
    end
end
xform(mime, ::CommonMark.AbstractContainer, node, entering, writer) =
    (node, entering)

parser = Parser()
enable!(parser, ShortcodeRule())
ast = parser("Click {{< icon star >}} here.")
html(ast; transform=xform)
"<p>Click <i class=\"icon-star\"></i> here.</p>\n"

Custom Delimiters

Change delimiters to match other template systems:

parser = Parser()
enable!(parser, ShortcodeRule(open="{%", close="%}"))

ast = parser("{% include header %}")
html(ast)
"{% include header %}\n"

String Macro

The @cm_str macro enables markdown string interpolation, embedding Julia expressions directly in markdown text.

using CommonMark

name = "world"
ast = cm"Hello *$(name)*!"
html(ast)
"<p>Hello <em><span class=\"julia-value\">world</span></em>!</p>\n"

Expressions are captured during parsing and evaluated at runtime when the code executes. This is useful for generating markdown programmatically without manually constructing nodes.

Default Syntax Rules

The macro enables several extensions by default, matching Julia's @md_str:

  • AdmonitionRule
  • AttributeRule
  • AutoIdentifierRule
  • CitationRule
  • FootnoteRule
  • MathRule
  • RawContentRule
  • TableRule
  • TypographyRule
DollarMathRule Conflict

DollarMathRule is NOT enabled because $ is used for interpolation. Use double backticks (``E=mc^2``) or math code blocks instead.

Custom Parser

Suffix the macro with a function name to use a custom parser:

# Define a minimal parser (no extensions)
minimal() = Parser()

text = "plain"
ast = cm"Just $(text) markdown."minimal
html(ast)
"<p>Just <span class=\"julia-value\">plain</span> markdown.</p>\n"

The suffix none invokes a basic parser with no extensions:

ast = cm"No **extensions** here."none
html(ast)
"<p>No <strong>extensions</strong> here.</p>\n"

Docstring Parser

Experimental

This feature is experimental and subject to change without notice.

Render module docstrings with CommonMark formatting instead of Julia's default markdown parser.

module MyPackage

# ... docstrings ...

using CommonMark
CommonMark.@docstring_parser
end

Call at module top-level after all docstrings are defined. Pass a custom parser to enable extensions:

CommonMark.@docstring_parser Parser(enable=[AdmonitionRule(), TableRule(), MathRule()])