Markdown (MyST)

ubCode can parse and preview Markdown as well as reStructuredText. Enabling Markdown for a project, and choosing its flavour (commonmark, gfm, or myst), happens in your project configuration through the [parse.parsers.md] table — see Parsers and file routing for the keys, file routing, and extension options.

The myst flavour is the full authoring surface. Markdown is rendered through the same preview engine as reStructuredText, so under myst the same directives and roles you already use in reStructuredText render identically in Markdown — you write them with MyST syntax rather than reStructuredText syntax.

This reference describes the Rendered preview mode; in Structural mode every directive and role renders as a uniform placeholder, identically for reStructuredText and MyST (see Authoring). For the handful of preview behaviours that apply to reStructuredText but not Markdown, see Differences from a Sphinx build.

Flavours

The flavour selects a base set of enabled syntax. Only myst understands directives and roles; choose it for Sphinx-Needs-style content in Markdown.

Flavour

What it enables

myst

CommonMark, GFM tables, and the full MyST surface: directives, roles, targets, front matter, and footnotes. Optional extensions (definition lists, dollar math, colon fences, …) are enabled individually.

gfm

GitHub Flavored Markdown: tables, strikethrough, task lists, autolinks, and GitHub-style alerts. No directives or roles.

commonmark

Plain CommonMark (the default). No tables, directives, roles, front matter, or footnotes.

What renders

Under the myst flavour the preview renders the following constructs. Several of them — task lists, GitHub-style alerts, definition and field lists, and math — are optional extensions: the gfm flavour turns task lists and alerts on by default, while under myst you enable the ones you need (see Parsers and file routing).

Construct

Syntax

Headings

# through ###### (and setext underlines); headings nest into sections.

Lists and task lists

- / * / 1.; task items - [x] and - [ ].

Tables

GFM pipe tables with per-column alignment.

Code blocks

Fenced (backtick or ~~~ fences) or indented; language taken from the fence info string.

Block quotes

> prefixed lines.

Alerts

> [!NOTE], > [!TIP], > [!IMPORTANT], > [!WARNING], > [!CAUTION] render as admonitions carrying the GitHub-style label.

Footnotes

reference markers [^label] and their [^label]: definitions.

Front matter

a leading --- … --- block.

Definition and field lists

a term line with : definition blocks; :name: value field lists.

Math

$…$ inline and $$…$$ block, typeset with KaTeX; a trailing $$…$$ (label) is recognised as the block’s label (the anchor and its ¶ permalink are emitted by a build, not in the preview). With the amsmath extension enabled, a bare \begin{align} … \end{align} environment (thirteen environment names, starred or not) typesets as a block without a $$ fence.

Thematic breaks

***, ---, or ___.

Links and images

[text](url), autolinks <https://…>, and ![alt](src) images. A link destination can also address project content — see Cross-referencing with links below. With the html_image extension enabled, an isolated <img src="…"> tag becomes a real image too — collected into the built site and checked, rather than passed through as written.

Attributes

{#id .class key=value} groups, inline or on a line of their own — see Attributes below.

Attributes

Added in version 0.35.0.

With the attrs_inline and attrs_block extensions enabled (see Parsers and file routing), an attribute group gives an element an id, CSS classes, and per-element options. A group is a brace-delimited list of

  • #name — the element’s id, which also becomes a cross-reference target (on a directive fence the id becomes the directive’s :name: option, which reaches the same anchor by the same rules; a name whose slug is empty — punctuation only, say, or, under [parse] id_style = "docutils", an all-CJK one — takes the page’s numbered id1 / id2 counter, as a Sphinx build does; see Differences from a Sphinx build),

  • .name — a CSS class (repeat for several; they combine),

  • key=value or key="value with spaces" — a per-element option,

  • %…% — a comment, which is dropped.

Inline, a group follows the element it describes immediately — one space between them and it stays literal text:

![diagram](d.png){#fig-flow .bordered width=400px align=center}
A `print(x)`{l=python} call and a [styled span]{#lead .intro}.
The [handbook](https://example.com){title="Team handbook" rel=noopener}.

Block, a group is a line of its own and attaches to the block below it. Blank lines between the two are fine, and consecutive groups stack — classes accumulate, and a later group’s id and keys win:

{#intro .lead}
The first paragraph of the page.

{style=lower-alpha start=3}
1. third
1. fourth

Every element accepts #id and .class. These also accept keys:

Element

Keys

Image

title, width (alias w), height (h), align (a, one of left/center/right).

Code span

language (aliases lexer, l).

Link

title on any link; rel and target on an external link only — an internal one becomes a cross-reference, whose HTML carries neither, so they are dropped with an informational attrs.unsupported_key. The class .external forces the destination to be treated as a plain URL rather than resolved against the project, which brings rel and target back.

Ordered list

start, style (decimal, lower-alpha, upper-alpha, lower-roman, upper-roman).

Block quote

attribution — rendered as the quote’s attribution line, with its own inline markup parsed.

Code block

lineno-start, emphasize-lines.

Definition list

the class .glossary turns the list into a glossary, so its terms become :term: targets.

Directive fence

every key becomes a directive option: #id is the directive’s :name:, .class is appended to its :class:, and an option the author wrote out wins over the group’s.

A key an element does not accept is dropped, and reported as attrs.unsupported_key (informational). A group with nothing below it, or above an element that cannot carry attributes, is reported as attrs.no_target / attrs.unsupported_target.

Three behaviours differ from MyST-Parser and are worth knowing — see Markdown attributes for the full list: classes accumulate on spans and links (MyST replaces them), an attribute group before a directive fence becomes directive options (MyST drops it), and ubCode reports the mistakes listed above where MyST is silent.

The .glossary class is the MyST-native way to write a glossary, and it is not the same thing as the {glossary} directive. A directive’s body is not Markdown: a {glossary} fence hands its raw lines to the glossary grammar, where an unindented line is a term and an indented block is its definition. So a : definition line written inside a {glossary} fence is read as a second term — on ubCode and on a Sphinx build alike — and is published as one in objects.inv. ubCode reports it as glossary.definition_as_term (see Linting); indent the definition under its term, or write the definition list with {.glossary} above it. The {.glossary} spelling needs the attrs_block extension: without it the attribute line is ordinary text and no glossary is created, which is what a Sphinx build does with it too.

Directives in MyST

A MyST directive is a fenced block whose info string is a name in braces. Three fence forms are recognised — a backtick fence, a tilde fence, or (only when the colon_fence extension is enabled) a colon fence:

```{name}
body
```

~~~{name}
body
~~~

:::{name}
body
:::

The argument is the text after {name} on the opening line; options are given either as :key: value lines or as a leading --- … --- YAML block; the remaining lines are the body.

Because Markdown renders through the same engine as reStructuredText, the same directive set is available and renders to the same rich shapes: admonitions, image and figure, diagram directives, need cards, if, version-change markers, and structural blocks. For what each family renders (and which fall back to a placeholder), see Directives; for need directives and their cards see Needs; for Mermaid and PlantUML see Diagrams.

A colon-fence admonition:

:::{note}
Colon fences avoid escaping backticks when the body contains fenced code.
:::

A need card, written with a backtick fence:

```{req} Login must be secure
:id: REQ_001
:status: open

The login form must be served over TLS.
```

Roles in MyST

An inline role is written {name}`text` — the role name in braces, immediately followed by the target text in backticks. The same roles available in reStructuredText are available in Markdown; see Roles for the full set and how each renders.

Cross-reference roles such as {ref} and {need} hydrate exactly as they do in reStructuredText: until the project has been indexed in the background they show as placeholder chips, and once indexing completes they become clickable links to their targets.

See {ref}`config_parsers` and the need {need}`REQ_001`.

Note that MyST has no bare-backtick role: single backticks are always inline code, never interpreted text (see Differences from a Sphinx build).

Footnotes

A Markdown footnote is a reference [^label] and a definition [^label]: text. By default the definitions do not render where they are written: they are collected to the foot of the page, re-sorted by the number each was given, and preceded by a horizontal rule — matching what a Sphinx build with MyST-Parser produces.

Ref b first[^b], then a[^a].

[^a]: Footnote a.

[^b]: Footnote b.

[^b] is referenced first, so it is numbered 1 and [^a] 2, and both definitions render at the foot of the page in that order.

Two keys under the parser’s table control this:

[parse.parsers.md]
footnote_sort = true         # the default
footnote_transition = true   # the default

footnote_sort turns off both halves when set to false: the definitions then render where they are written, in source order, auto footnotes are numbered in definition order rather than by first reference, and no rule is written even if footnote_transition is left on. footnote_transition = false on its own keeps the collection and drops only the rule.

Both keys apply to every Markdown parser, not only the myst flavour — gfm parses footnotes too. They are inert on commonmark, which has no footnote syntax at all.

On the myst flavour every well-formed [^label] is a reference, whether or not a definition for it was written nearby — which is how MyST-Parser reads them. A reference resolves against every definition on the finished page, including one inside a file pulled in with {include}, in either direction: the host can reference a definition in the included file, and the included file can reference one in the host. A reference nothing defines is reported — ref.unknown_footnote, plus ref.too_many_autonumbered once per page for a non-numeric label — and in body text it keeps its [^label] text without a link. In a heading the marker is dropped from the page title and from every list that names the page (the sidebar, a parent’s toctree entry, prev/next, the published inventory), while the heading itself still shows it. ref.unknown_footnote names the label as you wrote it, case and all, except a zero-padded number, which is reported as the number it becomes (see Differences from a Sphinx build). ref.too_many_autonumbered names no label: it reports the page’s supply of auto-numbered footnotes, not one reference. When the reference or the definition sits inside a file pulled in with {include}, the finding is reported at the host page’s first line and column rather than in the partial; the message still names the label where it has one (ref.unknown_footnote does; ref.too_many_autonumbered and std.footnote_unreferenced name none).

The reporting rule catches prose that merely looks like a label. A bare regex character class such as [^a-z] written in running text is a well-formed reference, and both ubCode and a Sphinx build report it. Wrapping it in a Markdown code span is the better remedy where the text is meant literally. Silencing it project-wide takes both codes, because a non-numeric label draws the second one too:

[lint]
ignore = ["ref.unknown_footnote", "ref.too_many_autonumbered"]

Note that ref.too_many_autonumbered also covers a genuine surplus of auto-numbered references ([#]_ and [#name]_) on reStructuredText pages in the same project, so silencing it is a broader step than it looks.

The gfm flavour is unaffected: there a [^label] with no definition stays plain text, exactly as its upstream plugin leaves it.

One difference in the block’s own markup: the rule carries class="footnotes" where a Sphinx build writes class="footnotes docutils" — ubCode writes <hr class="footnotes" /> and Sphinx <hr class="footnotes docutils" />. The extra class is one docutils adds to every transition and ubCode adds to none. Other differences a Markdown page with footnotes can meet are listed under Differences from a Sphinx build.

Differences from the reStructuredText preview

A few reStructuredText preview behaviours do not carry over to Markdown:

  • File splicing. The .. include:: mechanism does not apply to Markdown; a MyST {include} is spliced in the preview as it is in the build, and a reference resolves across the splice in either direction.

  • Prolog and epilog. rst_prolog and rst_epilog are applied to reStructuredText documents only; they add nothing to a Markdown preview.

  • No default role. Because single backticks are always inline code in MyST, there is no bare-interpreted-text default role, and the default_role setting has no effect on Markdown.

See Differences from a Sphinx build for the complete register of preview-versus-build differences.

See also