The MyST Abstract Syntax Tree (AST) is the structured representation of a MyST document after parsing. It is plain JSON, consumed by tools that render the document (to HTML, LaTeX, Typst, etc). This page is a brief overview of the structure and how the pieces fit together.
The spec and mystmd¶
This primer covers both the MyST Specification (this site) and mystmd (the official reference implementation):
The spec defines what a conforming MyST parser must produce - node types, their properties, and how they correspond to source. It evolves through the MyST Enhancement Proposal process.
mystmd is a command-line application that bundles several modular packages that make use of MyST.
myst-parseris a parser for this spec,myst-transformshave a collection of useful transforms to modify a MyST AST (e.g., resolving cross-references and appling numbering numbering).mystmdmay emits additional node types that aren’t yet in the spec as part of its build pipeline (like named parts).
If you’re writing a parser, stick to the spec. mystmd’s conventions (like specific named parts) aren’t part of the spec, but they’re still useful to know, since most MyST content today comes from mystmd. This page flags which is which as we go.
The shape of a node¶
Every node in the AST is a JSON object with a type field that names what kind of node it is.
Beyond type, a node has either a value or children:
Leaf nodes typically have a
value(typically a string).Parent nodes have a
childrenarray (more nodes).
Nodes may also carry additional properties - url on a link, depth on a heading, identifier on a cross-reference, etc.
These properties are specific to each node type and are documented in the node reference.
The interactive demo below lets you explore the AST for simple MyST text: a single line of text. Change the text to see the results, and click each box to see a different representation of the data, and stage in the processing pipeline (PRE or POST transforms).
Click the AST tab above to see the AST. (The tab displays YAML for readability; MyST sites serve it as JSON.)
You’ll find a root node (always the outermost node) wrapping a paragraph, which wraps a text leaf.
Inline formatting nests parent nodes¶
Inline formatting introduces parent nodes around the text they decorate.
In the AST tab, the paragraph’s children is a flat sequence of text, strong, and emphasis nodes. Each strong and emphasis wraps its own text node.
The markup characters (**, *) are gone from the source text. They’ve been turned into nested nodes (e.g. *word* becomes a type: emphasis node wrapping a text node).
Two processing stages: PRE and POST transforms¶
In the myst-cli, a MyST document is processed through a series of transformations, and the AST has a different shape after each one.
The short-hand for this in MyST documentation is PRE- and POST-transforms. You can get very granular by applying various myst-transforms in your own libraries or packages.
PRE: the output of parsing. The parser walks the source and expands directives and roles by calling their
runfunctions. Cross-references still look like ordinarylinknodes (e.g.url: "#some-label"). Directives appear asmystDirectivewrapper nodes containing their expanded children. Numbering has not yet been applied.POST: the output after transforms run over the PRE tree. Link nodes pointing at labels become
crossReferencenodes with resolved targets. Most directive wrappers are unwrapped, leaving only their semantic children. Numbering and enumeration are applied. This is the form most renderers consume.
You can see both stages inline: the {myst} directive used on this page exposes a PRE / POST toggle inside its AST tab, and so does the MyST sandbox. Try it on this cross-reference:
In PRE, the second paragraph contains an ordinary link node with url: "#my-label".
In POST, that same node has become a crossReference with identifier, kind, and resolved: true properties.
Which stage will you encounter?¶
The spec’s test cases describe the PRE form. They are the canonical “source -> AST” pairs that any conforming parser must produce.
The node reference documents node types that may appear in either stage. Some properties (like
resolvedoncrossReference, or computed numbering fields) appear only after transforms.
In short:
A parser produces PRE. Match the test cases.
A renderer receives POST. It’s what
myst buildemits.A transform pipeline applies a series of transformations that change the tree from
PREtoPOST
Directives in PRE vs. POST¶
Directives change shape between PRE and POST (roles behave similarly):
In PRE, you’ll see a mystDirective wrapper (name: note) containing an admonition child. The wrapper records the directive that produced it. The child is what its run function returned from a mystmd plugin.
In POST, the mystDirective wrapper is typically stripped, and transforms may add extra structure. For example, admonitionHeadersTransform inserts an admonitionTitle child.
When directive parsing fails, the POST AST signals it in one of two ways:
Unknown directive name (no plugin registered): the node stays as a
mystDirectiveleaf, with the original source text in itsvalue.Invalid arguments or body: the node’s type becomes
mystDirectiveError, and its children are dropped. Renderers can use this to display error messages in the output.
Plugins often emit a custom node that is transformed¶
A common plugin pattern uses a directive (or role) to emit a placeholder node, then a separate transform to expand that placeholder into real content.
For a working in-repo example of this pattern, see docs/templates.mjs (the myst:template directive emits a myst-template-ref placeholder, then mystTemplateTransform fetches template metadata and expands them).
This split is useful for anything you can’t do inside a directive’s run() function: async work, anything that needs the whole document for context, or anything that depends on cross-references being resolved first.
Frontmatter and document structure¶
A document’s frontmatter (the YAML at the top of a .md file) doesn’t appear as a node in the tree.
It is attached separately, alongside the mdast (the AST) in the JSON output of myst build.
A built page on disk looks roughly like:
{
"kind": "Article",
"frontmatter": { "title": "...", "authors": [...] },
"mdast": { "type": "root", "children": [ ... ] },
"references": { ... }
}A page from myst build --site
The mdast field is the tree you’ve been inspecting in the AST tabs above.
The references field holds cross-document data like resolved cross-references and citations, which a renderer needs when working across pages.
Chunking a page into blocks¶
The +++ syntax divides a page into block nodes. Each block wraps the content that follows it, and optional JSON after +++ becomes block metadata:
In the AST (POST), each chunk ends up inside a block node. Recognized keys (kind, class, label, identifier) are moved onto the block node, anything else is put under block.data. See Blocks & Comments for the formal block specification.
“Parts”: named regions of a page or project¶
Plugins and themes often need to pull out a specific region of a page (the abstract, the acknowledgments, etc.). MyST has “parts” for this.
Parts aren’t part of the myst-specification, they are a concept used by the MyST Document Engine to structure pages and projects.
See How parts appear in the AST for details.
How the MyST AST and its upstream foundation (mdast) relate¶
The MyST AST extends mdast, the Markdown AST used widely in the JavaScript unified ecosystem, which in turn builds on unist (Universal Syntax Tree).
If you’ve used remark or rehype, the shape will feel familiar.
MyST adds node types for directives, roles, cross-references, citations, admonitions, equations, and other constructs that don’t exist in plain Markdown.
Where to go next¶
The MyST sandbox parses any MyST input you type and shows the AST in PRE and POST stages.
The AST Node index describes the AST of all nodes defined in the specification.
The test cases provide source -> expected (PRE) AST pairs.
Generate MyST AST with Plugins in the MyST Guide shows a few ways to generate AST from a plugin.