Markdown

This content was generated with the assistance of AI and should be verified against the official documentation of each tool before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

This page was written against DocFX 2.81.0 (the latest release, published on 2026-09-25) and checked on 2026-10-26. Every example was built with docfx 2.81.0 inside the mcr.microsoft.com/dotnet/sdk:10.0 Docker image, using the default and modern templates, and the rendered output of the modern build was opened in a browser to see whether Mermaid and math diagrams appear. The articles are the conceptual (hand-written) content of the Pocket Units site; the generated API reference is covered in .NET API Docs.

What DocFX Flavored Markdown is

DocFX reads CommonMark-compliant Markdown through the Markdig parsing engine. On top of CommonMark it adds its own syntax, usually called DocFX Flavored Markdown or DFM: a YAML header, alerts, includes, code snippets that pull code from other files, tabs, video embeds and diagram blocks. Because these extensions are specific to DocFX, other renderers such as GitHub show them as plain text or as an ordinary block quote. The official page warns that previewing a file in a GitHub Flavored Markdown preview can therefore look different from the published site.

A few Markdig extensions are switched on by default: mathematics, emphasis extras (strikethrough), auto identifiers (heading anchors in the GitHub style), media links, pipe tables, auto links and emoji. Any other built-in Markdig extension is enabled by name in docfx.json, under build.markdownEngineProperties.markdigExtensions. A project that embeds DocFX as the Docfx.App NuGet package can instead configure the Markdig pipeline in code through the ConfigureMarkdig build option.

docfx.json (fragment)
{
  "build": {
    "markdownEngineProperties": {
      "markdigExtensions": [ "Footnotes", "Abbreviations" ]
    }
  }
}

The example above was built with only Footnotes listed, and a Markdown footnote ([^1]) rendered as a numbered reference with a footnote section at the end of the article. The Markdown page of the official documentation also carries a note saying that custom configuration of extensions through this property is not supported, which contradicts both the docfx.json reference (it describes the same property with a FootNotes example) and the build that was run; treat the working behaviour as authoritative for the extensions listed by name, and test extensions that need options.

The remaining sections follow the order in which the syntax is usually needed. The keys of markdownEngineProperties belong to the build section of the configuration file, described in docfx.json.

The Pocket Units articles

The examples use a small article folder next to the API reference of Pocket Units:

docs/
  docfx.json
  toc.yml
  index.md
  articles/
    toc.yml
    tour.md
  includes/
    requirements.md        (reusable fragment, excluded from the build)
  snippets/
    Usage.cs               (code that articles quote)
    convert.py
  src/PocketUnits/         (the C# library)

The configuration used for every build in this page is below. The exclude entry keeps the fragments in includes out of the published pages, and template lists default first and modern second so that the modern files win.

docfx.json
{
  "metadata": [
    {
      "src": [{ "src": "src", "files": ["**/*.csproj"] }],
      "dest": "api"
    }
  ],
  "build": {
    "content": [
      { "files": ["api/**.yml", "api/index.md"] },
      { "files": ["articles/**.md", "articles/**/toc.yml", "toc.yml", "*.md"] }
    ],
    "resource": [{ "files": ["images/**"] }],
    "exclude": ["includes/**"],
    "output": "_site",
    "template": ["default", "modern"],
    "markdownEngineProperties": {
      "alerts": { "PLANNED": "alert alert-secondary" },
      "markdigExtensions": ["Footnotes"]
    }
  }
}

The commands that produced the output shown below were docfx --version (it printed 2.81.0+ba844df...) and docfx docfx.json, run in the folder that holds docfx.json. The metadata stage restored and read the project, and the build stage printed its warnings; a successful run ends with Build succeeded with warning. when there are warnings and with Build succeeded. otherwise.

YAML header and uid

The YAML header (also called YAML front matter) annotates a Markdown file with metadata. It is a block delimited by --- lines at the very top of the file. Any key from the predefined metadata list of the docfx.json reference can be used in it, and front matter has the highest priority among the metadata sources: it wins over fileMetadata, which in turn wins over globalMetadata.

articles/tour.md (first lines)
---
uid: PocketUnits.Tour
title: Markdown tour
description: A tour of DocFX Flavored Markdown features.
---

# Markdown tour

The uid is a unique identifier of the file inside the project, used by cross-references (<xref:PocketUnits.Tour> or @PocketUnits.Tour, covered in Links and Cross References). Three rules from the documentation matter in practice:

  • If two files declare the same uid, which one a reference resolves to is undetermined, so keep UIDs unique.

  • API reference files get their UID from the mangled signature of the member (the UID of System.String is System.String), so you never write those by hand. Open a generated YAML file to see the value.

  • A conceptual Markdown file has no UID unless it declares one, so it cannot be a target of a cross-reference until you add the header. In the built page, the article element carries the value as data-uid; the page above rendered <article data-uid="PocketUnits.Tour">, and a file without the key rendered data-uid="".

Alerts

Alerts are block quotes that the templates render with a colour and an icon according to their significance. The syntax is a block quote whose first line is the alert keyword in [!KEYWORD] form:

> [!NOTE]
> Information the user should notice even if skimming.

> [!TIP]
> Optional information to help a user be more successful.

> [!IMPORTANT]
> Essential information required for user success.

> [!CAUTION]
> Negative potential consequences of an action.

> [!WARNING]
> Dangerous certain consequences of an action.

The five built-in keywords are NOTE, TIP, IMPORTANT, CAUTION and WARNING. The keyword is matched without regard to case (a block written with [!Warning] rendered as a warning), and a keyword that is not known is not an alert: > [!foo] stayed an ordinary block quote and printed the literal text [!foo], without a warning. The built page turned > [!NOTE] into the following HTML:

<div class="NOTE">
<h5>Note</h5>
<p>Lengths are converted through metres.</p>
</div>

Custom alerts

A project defines its own alert types with the build.markdownEngineProperties.alerts property. The key is the keyword without the surrounding [! and ], and the value is the CSS class names that go on the generated div:

{
  "build": {
    "markdownEngineProperties": {
      "alerts": {
        "PLANNED": "alert alert-secondary"
      }
    }
  }
}
> [!PLANNED]
> Add temperature units.

The build rendered this as <div class="alert alert-secondary"> followed by <h5>PLANNED</h5>. By default the title of an alert is its keyword in upper case. To change a title, the official page says to create a custom template (see Templates and Customization) and add a token.json file to it, whose keys are the alert keywords in lower case and whose values are the titles to display. That customisation was not executed here.

An alert is a block extension: the content on the same quoted lines is part of the alert. Text that is indented by four spaces after the keyword line becomes a code block inside the alert in DocFX, whereas GitHub shows it as a paragraph in a block quote.

Video and images

A block quote whose keyword is Video embeds a video from the link that follows it:

> [!Video https://www.youtube.com/embed/Sz1lCeedcPI]

The build produced a div with the class embeddedvideo that wraps an iframe whose address was rewritten to the youtube-nocookie.com host with rel=0. Images use the ordinary Markdown syntax, ![alt text](path/to/image.png); files that are referenced must be inside a resource mapping of the build (the sample configuration lists images/**), otherwise they are not copied to _site.

Math expressions

DocFX renders LaTeX math through MathJax. An inline expression is delimited by single dollar signs, and a block expression by double dollar signs on a line of its own:

Inline math $\sqrt{3x-1}+(1+x)^2$ and a block:

$$d_\text{m} = d \cdot f_\text{unit}$$

The official documentation states that math is only supported in the modern template. That is what the two builds showed. The HTML of both templates contains <span class="math">\(...\)</span> elements. In the modern build the page also loads the bundled MathJax script from its public folder, and the browser replaced both spans by mjx-container elements (two were found on the page). The default build loads no MathJax script, so its spans keep the raw \(...\) text.

Mermaid diagrams

A fenced code block whose language is mermaid becomes a diagram:

```mermaid
flowchart LR
    A[Kilometre] -->|x 1000| B[Metre] -->|/ 1609.344| C[Mile]
```

As with math, this works only in the modern template. Both builds emit the same HTML, <pre><code class="lang-mermaid">...</code></pre>. With modern the page loads the bundled Mermaid runtime (the public folder contained the Mermaid core and one script per diagram type, so no CDN is needed) and the browser converted the block into an element pre.mermaid holding an svg. With the default template no Mermaid script is loaded and the visitor sees the diagram source in a code block. Every diagram type that Mermaid supports can be used, for example pie, Gantt, user journey and class diagrams; DocFX does not restrict the language.

PlantUML diagrams

A fenced block with the language plantuml is rendered as an image. The block below is a complete sequence diagram of one line:

```plantuml
Bob -> Alice : hello
```

In the build above this produced a div of class lang-plantUml with the diagram as an inline svg element, and it appeared with both the default and the modern template, because the picture is made at build time and not in the browser. By default the drawing is requested from the public PlantUML server, so the build needs network access. The settings live under build.markdownEngineProperties.plantUml:

Setting Meaning Default

renderingMode

remote or local

remote

remoteUrl

Address of the PlantUML server, needed only in remote mode

http://www.plantuml.com/plantuml/

outputFormat

svg, ascii or ascii_unicode

svg

localPlantUmlPath

Path to plantuml.jar, for local mode

Looked up in the project directory

localGraphvizDotPath

Path to the Graphviz dot executable, needed in local mode for every diagram that is not a sequence diagram

None

javaPath

Path to the Java installation

The JAVA_HOME environment variable

{
  "build": {
    "markdownEngineProperties": {
      "plantUml": {
        "localPlantUmlPath": "path/to/plantuml.jar",
        "renderingMode": "local"
      }
    }
  }
}

Local rendering was not executed here, because it needs a Java runtime and a PlantUML jar that the SDK image does not contain. For documentation that must build offline, or that contains private diagrams, use local, since in remote mode the text of every diagram is sent to the configured server.

Includes

An include replaces a reference with the contents of another Markdown file at build time, which avoids repeating a paragraph in several articles. There are two forms. The block form sits alone on its line and inserts a whole file between the paragraphs around it. The inline form is part of a sentence:

[!INCLUDE [requirements](../includes/requirements.md)]

Inline: before [!INCLUDE [requirements](../includes/requirements.md)] after.

The text in square brackets is a title (the official page describes it as the name of the file), and the path in parentheses is relative to the file that contains the include.

The fragment file itself must be kept out of the build, usually by placing it in a folder named includes and listing that folder in build.exclude. When the entry "exclude": ["includes/**"] was removed and includes/**.md was added to the content mapping, DocFX published _site/includes/requirements.html as a stand-alone page next to the real articles; with the exclusion in place no such file exists. The fragment still resolves for the including article because includes are read from disk, not from the list of built files.

If the target does not exist, the build warns and leaves the text of the include in the page:

/w/articles/more.md: warning include-not-found: Invalid include link: '../includes/missing.md'.

The rendered paragraph then shows [!include[missing](../includes/missing.md)] literally, so a broken include is easy to spot in a preview but it does not fail the build unless warnings are treated as errors (see CLI).

Code snippets

A code snippet link inserts code from another file, so the examples in an article are real, compilable source that is tested and reformatted with the rest of the project. The link is a block that stands alone on its line:

[!code-<language>[](<filepath><query-options>)]

The parts are the language (the Markdown highlighting identifier, such as csharp), a bracket text (empty in the official examples), the path of the file relative to the article, and an optional selector. A title can be added in quotes after the selector, as in [!code-csharp[](Usage.cs#L9-L10 "Title here")]; the build put it on the generated element as a title attribute.

Anatomy of a code snippet link and how a tagged region becomes a code block

The figure shows how the parts of a link map to the extraction of a tagged region from a source file.

The Pocket Units source files

The articles quote two files. Usage.cs carries a region, a pair of tag comments and a second tagged block, and convert.py carries a tag comment in Python:

snippets/Usage.cs
using PocketUnits;

// <imports>
using System.Globalization;
// </imports>

#region Convert10km
double miles = Converter.Convert(10, LengthUnit.Kilometre, LengthUnit.Mile);
Console.WriteLine(miles.ToString("F3", CultureInfo.InvariantCulture));
#endregion

// <roundtrip>
double back = Converter.Convert(miles, LengthUnit.Mile, LengthUnit.Kilometre);
Console.WriteLine(back.ToString("F3", CultureInfo.InvariantCulture));
// </roundtrip>
snippets/convert.py
from pocket_units import convert

# <py_convert>
print(round(convert(10, "km", "mi"), 3))
# </py_convert>

Selecting lines

Four selectors choose which lines of the file appear. Each can be written in the # form or the ?name= form, and the build printed what the comments below say:

<!-- region declared with #region and #endregion -->
[!code-csharp[](../snippets/Usage.cs#Convert10km)]

<!-- tag comment, written as a query -->
[!code-csharp[](../snippets/Usage.cs?name=roundtrip)]

<!-- lines 9 and 10 of the file, 1-based -->
[!code-csharp[](../snippets/Usage.cs#L9-L10)]

<!-- lines 5 to 7 and line 9, with the second and third of those highlighted -->
[!code-csharp[](../snippets/Usage.cs?range=5-7,9&highlight=2-3)]

<!-- a single line, then an explicit start and end -->
[!code-csharp[](../snippets/Usage.cs?start=9&end=10)]

The first link printed two lines, the body of the region, without the #region and #endregion lines:

double miles = Converter.Convert(10, LengthUnit.Kilometre, LengthUnit.Mile);
Console.WriteLine(miles.ToString("F3", CultureInfo.InvariantCulture));

The rules, taken from the documentation and from the DocFX source code (the CodeSnippet folder of Docfx.MarkdigEngine.Extensions), are:

Selector Result

#TagName or ?name=TagName

The lines between the start and end marker of the tag. The marker lines are not included. Tag names may contain letters, digits, dots, hyphens and underscores, and are matched without regard to case.

#L12-L16

Lines 12 to 16 of the file, counting from 1, both included. Because it is a plain line range, marker lines inside the range are shown.

?range=5-7,9

A list of lines or line ranges. The range and highlight options accept the same syntax.

?start=6&end=10

Lines from start to end. If only start is given the snippet is that single line (?start=6 printed one line).

?highlight=2,5-7,9-

Marks lines of the output for highlighting: lines 2, 5 to 7 and 9 to the end of the snippet. The generated code element received the attribute highlight-lines="2-3" (for the example with highlight=2-3); drawing the highlight is up to the template.

?dedent=2

Number of leading characters removed from each line. Without it, the common indentation of the selected lines is removed; dedent=0 keeps the original indentation.

Query options can be combined with &. In the build, a body indented eight spaces inside a namespace and a class was printed flush left by default, and with ?name=body&dedent=0 it kept all eight spaces.

Tag syntax by language

A tag is a pair of marker lines written as a comment of the language of the file, which is what lets the same syntax work in C#, Python or XML. The marker format is chosen from the file extension, using the table in the DocFX source:

Languages Start marker End marker

C#, Visual Basic and C-like languages such as Java, JavaScript, TypeScript, Go, Rust, Swift, C and C++

// <name>

// </name>

C# regions

#region name

#endregion

Visual Basic comments

' <name>

' </name>

Visual Basic regions

#Region name

#End Region

XML, XAML, HTML and Handlebars

<!-- <name> -->

<!-- </name> -->

Python, PowerShell, Perl, R, Ruby, shell

# <name>

# </name>

SQL, Haskell and Lua

-- <name>

-- </name>

CSS

/* <name> */

/* </name> */

Batch files

rem <name>

rem </name>

Erlang and MATLAB

% <name>

% </name>

Lisp

; <name>

; </name>

The matching ignores white space between the characters of a marker, so // <name> and //<name> are the same. For C# and Visual Basic regions the end marker does not name the tag, so the nearest open region is closed. The language used to pick the marker format comes from the file extension of the path, and aliases are understood (for example cs for csharp, py for python and js for javascript). When the extension is not known, DocFX tries a default language and may not find the tag; the range and start/end selectors do not depend on markers.

The generated HTML for a snippet is a pre with a code whose class is lang- plus the language you wrote, so it is highlighted by the template’s highlighter like any fenced block.

Errors and warnings

Three situations produce messages that are easy to miss in a long build log:

Situation Message in the build log Result in the page

The file or tag cannot be found

warning codesnippet-not-found: Invalid code snippet link: '../snippets/Nope.cs'.

A div with the class WARNING and the text "It looks like the sample you are looking for does not exist."

The language is not one DocFX knows

warning unknown-language-code: Unrecognized language value 'klingon' in code snippet ...

The snippet is rendered with the class lang-klingon, so it is not highlighted

A query option that DocFX does not know, such as ?bogus=1, is silently ignored by the parser: the link was accepted and the whole file was inserted, with no warning at all. Per the source, option keys are compared in lower case, but a misspelled key is not reported, so check the first build of every new snippet.

The DocFX source also recognises [!notebook-...] links that select a named cell of a Jupyter notebook in the same way, but the Markdown page of the official documentation does not describe them, so this page does not rely on them.

Tabs

Tabs show one of several variants of a section and remove duplicated content. A tab starts with a heading whose only content is a link to #tab/id; its text becomes the tab title, and the content goes up to the next tab heading or to the end marker:

# [Linux](#tab/linux)

Linux content.

# [Windows](#tab/windows)

Windows content.

---

All tabs on a page that share the same id stay in sync: choosing windows in one group selects windows in every other group of the page. The build turned the example into a div.tabGroup containing a ul of a elements with role="tab" and a section with role="tabpanel" per tab, and the unselected panels were marked hidden. After the page was opened in the browser the address bar showed ?tabs=linux%2Cdotnet, so the selected tab ids are written to the tabs query parameter so the selection is kept in the address.

The end of a tab group is marked by a horizontal rule. The documentation text says to use ***, while all of its examples use ---; both ended the group in the build (a group ended with *** and the following paragraph was outside the tabs). Use --- as the examples do, and leave a blank line before and after it. The official examples write every tab heading as a level-one heading (#).

Dependent tabs

A group can make one set of tabs depend on the selection of another. Add a second id after the tab id: #tab/dotnet/linux is the .NET tab that is visible when the linux tab is selected in the earlier group.

# [.NET](#tab/dotnet/linux)

.NET content for Linux.

# [.NET](#tab/dotnet/windows)

.NET content for Windows.

# [Python](#tab/python)

Python content for every platform.

---

In the HTML, the two .NET tabs carry data-tab="dotnet" and data-condition="linux" or "windows", and the one that does not match the current condition is hidden by aria-hidden="true". The Python tab has no condition, so it is always shown, and it is the one whose content does not vary with the platform.

Build-time processing

The sequence below summarises what happens to the extension syntax in a docfx build. Includes and snippets are resolved at build time by reading files on disk, alerts and tabs become HTML structure, and only Mermaid and math are completed later by the browser, which is why they need the modern template’s scripts.

flowchart LR MD["Markdown article
with DFM syntax"] --> PARSE["Markdig parser
with DocFX extensions"] INC["includes/*.md
snippets/*.cs"] -->|"read from disk"| PARSE PARSE --> HTML["HTML in _site"] HTML -->|"modern template"| BR["Browser runs Mermaid
and MathJax"] HTML -->|"default template"| RAW["Mermaid and math stay
as source text"]

Mermaid flowchart of the processing of DocFX Flavored Markdown at build time and in the browser.

Pitfalls and version notes

  • Use the modern template for Mermaid and math. If template lists only default, the diagram stays a code block and math stays as raw \(...\) text, with no warning at build time.

  • Exclude fragments that are only meant for [!INCLUDE] from the build, or they become pages of their own.

  • A snippet that cannot be resolved is a warning, not an error, and the page still builds with an in-page "sample does not exist" notice. Run docfx build with warnings treated as errors in CI.

  • Markdown that uses DFM extensions renders differently on GitHub. Do not rely on a repository preview to judge the result.

  • A tab heading consists of a single link whose target starts with #tab/; anything else is an ordinary heading.

  • An alert keyword that is neither one of the five built-in ones nor defined in alerts leaves a plain block quote.

  • The PlantUML default sends diagram text to a public server; switch to local for private content or offline builds.

  • DocFX 2.81.0 documents its Markdown features on a single page; nothing on that page is marked with the release that added it, so check the release notes of DocFX on GitHub when an older version lacks a feature.