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 |
|---|---|
|
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. |
|
GitHub Flavored Markdown: tables, strikethrough, task lists, autolinks, and GitHub-style alerts. No directives or roles. |
|
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 |
|
Lists and task lists |
|
Tables |
GFM pipe tables with per-column alignment. |
Code blocks |
Fenced (backtick or |
Block quotes |
|
Alerts |
|
Footnotes |
reference markers |
Front matter |
a leading |
Definition and field lists |
a term line with |
Math |
|
Thematic breaks |
|
Links and images |
|
Attributes |
|
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 numberedid1/id2counter, as a Sphinx build does; see Differences from a Sphinx build),.name— a CSS class (repeat for several; they combine),key=valueorkey="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:
{#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 |
|
Code span |
|
Link |
|
Ordered list |
|
Block quote |
|
Code block |
|
Definition list |
the class |
Directive fence |
every key becomes a directive option: |
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.
Cross-referencing with links¶
A Markdown link destination does not have to be a web address. Where it names project content, the link becomes a cross-reference: the destination is rewritten to the page it resolves to, and a link you gave no text takes the target’s title.
Destination |
Resolves to |
|---|---|
|
A target. |
|
Another source document, by path relative to this one.
A leading |
|
A place inside another document — a target that document defines,
or one of its headings.
The document may be reStructuredText: |
|
The same as |
|
A file to download. The file is published beside the site and the link
offers it to the reader to save.
The two spellings differ over a |
|
An object in another project’s inventory
(see Intersphinx).
Narrow the search with up to three filters —
|
|
An external link, unchanged. |
Note
Linking to a heading.
A #fragment reaches a heading directly, on the referring page or in
another document, with no explicit target:
## Some Heading
See [](#some-heading), or [](guide.md#other-heading).
A link with no text of its own then reads the heading’s words — Some Heading — rather than the raw id.
A heading is the LAST thing tried, so an explicit (target)=,
a project-wide label, a document of that name or an intersphinx match
all win over a same-named heading on the referring page.
A destination that resolves to nothing keeps its text and stays a link,
so a broken reference is visible rather than silent,
and it is reported —
as std.ref or std.doc for project content,
intersphinx.ref for an inventory search,
or download.not_readable for a file that cannot be read.
The link points at # plus the name that could not be found:
the destination itself for a target or document,
and the #fragment alone where the document was found
but the place inside it was not
(the text then reads document#fragment, naming both halves).
Custom URL schemes¶
Declare other schemes under url_schemes to have them treated as external
links, optionally rewriting them:
[parse.parsers.md]
url_schemes = ["doi"] # recognise, rewrite nothing
# …or rewrite as well as recognise:
[parse.parsers.md.url_schemes]
doi = "https://doi.org/{{path}}"
gh = { url = "https://github.com/{{path}}", title = "GitHub: {{path}}" }
A template may substitute any part of the destination —
{{uri}}, {{scheme}}, {{netloc}}, {{path}}, {{params}},
{{query}} and {{fragment}}.
Substitution is plain text replacement, not Jinja:
any other {{name}} is left in the result as written,
and a part the destination does not have substitutes as the empty string.
{{params}} is always empty, so {{path}} keeps any ;segments.
title supplies the link text when you wrote none,
and classes adds HTML classes after any you attached inline.
inv, path and project are reserved for cross-referencing
and cannot be redefined as URL schemes;
an entry naming one is reported and ignored.
Turning cross-referencing off¶
Set all_links_external to treat every Markdown link
as a plain external URL:
[parse.parsers.md]
all_links_external = true
Nothing in the table above then applies.
Every destination — #target, a document path,
project:, path:, inv:,
and any scheme you declared under url_schemes —
reaches the page exactly as you wrote it,
no url_schemes template is applied,
and no link is reported as unresolved.
Use it for content that is authored elsewhere and only rendered here, where the destinations mean something to that other system.
Opening external links in a new tab¶
[parse.parsers.md]
links_external_new_tab = true
A link that renders as an external URL then carries
target="_blank" and rel="noreferrer noopener".
That is every plain web link,
every autolink whose scheme you did not declare,
and every destination a url_schemes entry rewrote.
Cross-references, downloads and inv: links are unaffected —
a link into your own project stays in the same tab.
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_prologandrst_epilogare 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_rolesetting has no effect on Markdown.
See Differences from a Sphinx build for the complete register of preview-versus-build differences.
See also
Parsers and file routing — enabling Markdown, flavours, and extensions.
Directives — the directive set shared with reStructuredText.
Roles — the role set shared with reStructuredText.
Differences from a Sphinx build — where the preview differs from a Sphinx build.