Extensions
Extensions add syntax beyond the CommonMark specification. They must be explicitly enabled with enable!.
using CommonMarkTables
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.
Footer
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.
Reference Links
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
endUndefined: label='missing', style=full, image=falseRaw 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:
AdmonitionRuleAttributeRuleAutoIdentifierRuleCitationRuleFootnoteRuleMathRuleRawContentRuleTableRuleTypographyRule
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
Render module docstrings with CommonMark formatting instead of Julia's default markdown parser.
module MyPackage
# ... docstrings ...
using CommonMark
CommonMark.@docstring_parser
endCall at module top-level after all docstrings are defined. Pass a custom parser to enable extensions:
CommonMark.@docstring_parser Parser(enable=[AdmonitionRule(), TableRule(), MathRule()])