Skip to content

The Markdoc syntax

Markdoc syntax is a superset of Markdown, specifically the CommonMark specification. Markdoc adds a few extensions to the syntax, such as tags and annotations, which we describe below. These extensions enable Markdoc's powerful extensibility model.

For a formal grammar of the Markdoc tag syntax, refer to the Markdoc syntax spec.


Nodes are elements that Markdoc inherits from Markdown, which you can customize with annotations.

# Headers





- Item 1
- Item 1
- Item 1

> Quotes

`Inline code`

Code fences






  • Item 1
  • Item 1
  • Item 1


Inline code

Code fences

For more information, check out the Nodes docs.


Tags are the main syntactic extension that Markdoc adds on top of Markdown. Each tag is enclosed with {% and %}, and includes the tag name, attributes, and the content body.

Similar to HTML, you can nest Markdoc tags, and customize them with attributes.

{% tag %}
{% /tag %}

Tags can be self-closing (similar to HTML). In this example, you'll see that the content body is removed, and that the tag is closed with a /.

{% image width=40 /%}

If your tag doesn't contain any new lines, then it's treated as an inline tag. Inline tags are automatically wrapped with a single paragraph Node (which renders a <p> element by default), to follow the CommonMark paragraph spec.

{% code %}

{% highlight %}Inline tag 1{% /highlight %}
{% highlight %}Inline tag 2{% /highlight %}

{% /code %}

For more information, check out the Tags docs.


Pass attributes to nodes and tags to customize their behavior. You can pass values of type: number, string, boolean, JSON array, or JSON object, either directly or using variables.

With tags, you can use an HTML-like syntax:

{% city
   name="San Francisco"
   coordinates=[1, 4, 9]
   meta={id: "id_123"} 
   color=$color /%}

Because the HTML-like syntax doesn't work with nodes, we offer another option: write the attributes after the tag or node you're passing them to, in a separate set of {% and %}.

{% table %}

- Function {% width="25%" %}
- Returns  {% colspan=2 %}
- Example  {% align="right" %}

{% /table %}

For more information, check out the Attributes docs.


Markdoc variables let you customize your Markdoc documents at runtime. Variables all have a $ prefix.

Here I am rendering a custom {% $variable %}

Variables must contain JSON-serializable content, such as strings, booleans, numbers, arrays, and JSON objects.
You can access nested values using dot-notation, similar to JavaScript:

Here's a deeply nested variable {% $markdoc.frontmatter.title %}

You can use variables throughout your document, as content itself:

© {% $currentYear %} Stripe

For more information, check out the Variables docs.


Functions look and feel similar to JavaScript functions. They're callable from the body of the document, inside an annotation, or within tag attributes. Function parameters are comma-separated. Trailing commas aren't supported in function calls.

# {% titleCase($markdoc.frontmatter.title) %}

{% if equals(1, 2) %}
Show the password
{% /if %}

{% tag title=uppercase($key) /%}

For more information, check out the Functions docs.



Note: comment support currently requires passing allowComments: true to Markdoc.Tokenizer.
This will be on by default in a future version of Markdoc.

Markdoc supports Markdown comment syntax adding comments to your documents without having the content show up in the renderable output.

<!-- comment goes here -->


This table outlines the various options you can pass to Markdoc.transform. Each option adjusts how a document is transformed and rendered.

nodes{ [nodeType: NodeType]: Schema }Register custom nodes in your schema
tags{ [tagName: string]: Schema }Register custom tags in your schema
variables{ [variableName: string]: any }Register variables to use in your document
functions{ [functionName: string]: ConfigFunction }Register custom functions to use in your document
partials{ [partialPath: string]: Ast.Node }Register reusable pieces of content to used by the partial tag

Full example

Here's an example of what a Markdoc config would look like:

const config = {
  nodes: {
    heading: {
      render: 'Heading',
      attributes: {
        id: { type: String },
        level: { type: Number }
  tags: {
    callout: {
      render: 'Callout',
      attributes: {
        title: {
          type: String
  variables: {
    name: 'Dr. Mark',
    frontmatter: {
      title: 'Configuration options'
  functions: {
    includes: {
      transform(parameters, config) {
        const [array, value] = Object.values(parameters);

        return Array.isArray(array) ? array.includes(value) : false;
  partials: {
    '': Markdoc.parse(`# My header`)

const content = Markdoc.transform(ast, config);

Next steps