Basic Concepts and Pipeline
|
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 on NuGet, published on 2026-09-25) and checked on
2026-10-26. The commands below were run with DocFX 2.81.0 installed as a .NET tool inside the
mcr.microsoft.com/dotnet/sdk:10.0 Docker image (SDK 10.0.403) against the Pocket Units C# library, so the listings show
real output. DocFX is community-maintained under the .NET Foundation; Microsoft Learn no longer uses it. See
the DocFX overview for governance and when to choose it.
What DocFX does and why it has stages
DocFX is a command-line tool that brings together two kinds of content in one site: hand-written Markdown pages (articles) and API reference pages generated from .NET source code. For the API part it relies on the XML documentation comment syntax that C# and Visual Basic already have, so no second documentation format has to be learned. Both languages can be read, but the generated signatures are always shown in C# syntax.
The work is split into stages that can be run on their own, because each one consumes the files the previous one produced:
-
metadata reads the .NET projects and writes YAML files that describe every namespace, type and member.
-
build combines that YAML with the Markdown articles and table-of-contents files and renders the result with a template into HTML (or a JSON model).
-
pdf (optional) turns the built site into PDF files.
Running the stages separately is useful to understand what DocFX does, to inspect the intermediate YAML, and to regenerate only the part that changed. In day-to-day use one command runs everything:
docfx docfx.json
Source: DocFX docs: Basic Concepts
Without a sub-command, docfx runs the metadata, build and pdf commands in turn, as the
command-line overview lists. The pdf step
only produces files for tables of contents that opt in with pdf: true; the site built in this page does not, and its
log shows no pdf activity.
The pipeline at a glance
The figure below follows the data. Each box is a file or folder you can open and read; each arrow is a DocFX command or an internal step of it.
.csproj, .sln, .dll + .xml"] -->|"docfx metadata
Roslyn"| YML["YAML files
api/*.yml + toc.yml"] MD["Markdown articles
and toc.yml files"] --> BLD YML -->|"docfx build"| BLD{{"resolve xrefs
Markdig: Markdown to HTML
templates run by Jint"}} TPL["Templates
default, modern, custom"] --> BLD BLD --> SITE["_site
HTML, toc.json, xrefmap.yml,
manifest.json"] SITE -->|"docfx pdf
optional"| PDF["toc.pdf files"] SITE -->|"docfx serve"| WEB["http://localhost:8080"]
Pipeline of the DocFX commands, from .NET source and Markdown to the _site folder, PDF files and a local preview.
Everything is driven by one JSON file, docfx.json, which has a metadata section for the first stage and a build
section for the second. Its keys are covered in docfx.json.
The Pocket Units project
All commands in this page run in a small project that documents the Pocket Units length converter. The folder looks like this:
docfx.json
index.md
toc.yml
src/PocketUnits/PocketUnits.csproj
src/PocketUnits/Converter.cs
src/PocketUnits/LengthUnit.cs
The project file targets net8.0 and sets GenerateDocumentationFile, which is good practice for a library but is not
what DocFX reads (see the metadata stage below). The code carries ordinary XML documentation comments; this is the
converter, trimmed to the part that matters:
namespace PocketUnits;
/// <summary>Converts lengths between <see cref="LengthUnit"/> values.</summary>
public static class Converter
{
/// <summary>Converts <paramref name="value"/> from one unit to another.</summary>
/// <param name="value">The magnitude to convert.</param>
/// <param name="from">The unit of <paramref name="value"/>.</param>
/// <param name="to">The target unit.</param>
/// <returns>The converted magnitude.</returns>
public static double Convert(double value, LengthUnit from, LengthUnit to)
=> value * ToMetres(from) / ToMetres(to);
private static double ToMetres(LengthUnit unit) => unit switch
{
LengthUnit.Metre => 1.0,
LengthUnit.Kilometre => 1000.0,
LengthUnit.Foot => 0.3048,
LengthUnit.Mile => 1609.344,
_ => throw new ArgumentOutOfRangeException(nameof(unit))
};
/// <summary>Internal helper that must not appear in the API reference.</summary>
/// <exclude />
public static void Reset() { }
}
Source: DocFX docs: .NET API Docs
The configuration used throughout is the smallest one that produces a complete site:
{
"metadata": [
{ "src": [ { "src": "src", "files": [ "**/*.csproj" ] } ], "dest": "api" }
],
"build": {
"content": [ { "files": [ "api/**.yml", "api/index.md", "toc.yml", "*.md" ] } ],
"output": "_site",
"template": [ "default", "modern" ]
}
}
Source: DocFX docs: Template
The src entry says where the projects are and which files in that folder to take, dest says where the generated YAML
goes, content lists the files the build step reads, output is the site folder and template selects the look. The
meaning of each key, and the many other options, are in docfx.json; how
the C# comments become pages is in DotNet API Docs. The comment
syntax itself is covered in C# coding conventions and documentation
and, for Visual Basic, in
Visual Basic XML documentation, analyzers and code style;
the project and tooling side is in C# build and tooling.
Stage 1: metadata
The first stage is run with docfx metadata docfx.json. It reads the sources listed in the metadata section and
searches them for XML documentation entries. A detail that surprises people: for project input DocFX does not read the
.xml file the compiler can emit; it uses the Roslyn compiler to walk the code itself. A project can also be replaced by
compiled .dll or .exe files together with their .xml files (see
DotNet API Docs).
Running it on Pocket Units printed:
Loading project /w/src/PocketUnits/PocketUnits.csproj
Determining projects to restore...
Restored /w/src/PocketUnits/PocketUnits.csproj (in 858 ms).
Processing PocketUnits
Creating output...
Build succeeded.
0 warning(s)
0 error(s)
Restore ran automatically before the project was loaded. The folder named by dest then contains:
api/PocketUnits.yml
api/PocketUnits.Converter.yml
api/PocketUnits.LengthUnit.yml
api/toc.yml
api/.manifest
There is one YAML file per namespace and per type, plus a generated table of contents. This is the only
auto-generated toc.yml: every other table of contents is written by hand. The head of the file for the class shows
the shape of the data (listing shortened; the real file continues with the members and their syntax):
### YamlMime:ManagedReference
items:
- uid: PocketUnits.Converter
commentId: T:PocketUnits.Converter
id: Converter
parent: PocketUnits
children:
- PocketUnits.Converter.Convert(System.Double,PocketUnits.LengthUnit,PocketUnits.LengthUnit)
langs:
- csharp
- vb
name: Converter
nameWithType: Converter
fullName: PocketUnits.Converter
type: Class
assemblies:
- PocketUnits
namespace: PocketUnits
summary: Converts lengths between <xref href="PocketUnits.LengthUnit" data-throw-if-not-resolved="false"></xref> values.
syntax:
content: public static class Converter
content.vb: Public Module Converter
Source: DocFX docs: Basic Concepts
Three things are worth noticing:
-
The first line,
### YamlMime:ManagedReference, names the kind of file. DocFX picks a document processor from it (see the build stage). -
The
uidis the unique identifier of the API item. For members it is the full signature including the namespace and parameter types. You need it whenever a Markdown article links to an API item; see Links and Cross References. -
The
<see cref="LengthUnit"/>of the comment became anxrefelement. It is resolved later, in the build stage, which is why the YAML can be produced without knowing where the target page will live.
The member marked with <exclude /> in the C# source does not appear in the YAML at all: searching the generated
PocketUnits.Converter.yml for Reset finds nothing, and neither does the rendered page.
Stage 2: build
The second stage is run with docfx build docfx.json. Appending --serve hosts the result on a local web server for
preview. According to the official description, the stage does four things:
-
resolves all cross-references;
-
converts the YAML of the metadata stage into a structured data model for the template engine;
-
converts all Markdown into HTML;
-
applies the templates and themes.
The log of the Pocket Units build names the processors that handled each kind of input:
Searching built-in plugins in directory /root/.dotnet/tools/.store/docfx/2.81.0/docfx/2.81.0/tools/net10.0/any/...
7 plug-in(s) loaded.
Building 2 file(s) in TocDocumentProcessor(BuildTocDocument)...
Building 3 file(s) in ManagedReferenceDocumentProcessor(BuildManagedReferenceDocument=>SplitClassPageToMemberLevel=>ValidateManagedReferenceDocumentMetadata=>ApplyOverwriteDocumentForMref=>FillReferenceInformation=>FillMetadata)...
Building 1 file(s) in ConceptualDocumentProcessor(BuildConceptualDocument=>ValidateConceptualDocumentMetadata)...
Applying templates to 6 model(s)...
XRef map exported.
Build succeeded.
0 warning(s)
0 error(s)
-
TocDocumentProcessorhandled the twotoc.ymlfiles (the hand-written one and the generatedapi/toc.yml). -
ManagedReferenceDocumentProcessorhandled the three API YAML files. Its steps show where overwrite files are applied (ApplyOverwriteDocumentForMref) and where references are filled in. -
ConceptualDocumentProcessorhandled the Markdown article,index.md. -
The six models (two tables of contents, three API pages and one article) were then handed to the templates.
Markdown: Markdig
Markdown is converted to HTML with Markdig, a CommonMark-compliant processor written
in C#. DocFX adds its own flavor on top (alerts, file inclusion, cross-references, tabs, YAML headers, and so on), which
is described in Markdown. Markdig extensions can be switched on through the
markdownEngineProperties key of the build section.
Templates: Jint
Template processing is the one part of DocFX that is not written in C#. A template is a folder of files that includes JavaScript, and those scripts are executed by the Jint JavaScript interpreter inside the .NET process. The benefit is that the build can be customised by overriding scripts without compiling anything. In practice most sites only change styles and a few metadata values; templates are the subject of Templates and Customization.
The template array in docfx.json is a search path, not a list of alternatives. For each file the engine looks in
the folders from the last entry to the first:
{
"build": {
"output": "_site",
"template": [ "default", "modern", "templates/mytemplate" ]
}
}
Source: DocFX docs: Basic Concepts
With this setting a file is looked up in templates/mytemplate first, then in modern, then in default. The official
Template page recommends [ "default", "modern" ] for new sites because the modern template adds dark mode, more
features and more customisation options. The default and modern templates are embedded in the DocFX installation.
Two commands help to see them:
docfx template list
docfx template export modern -o exported/modern
docfx template list printed the names of the five embedded templates:
modern
default(zh-cn)
common
statictoc
default
Version 2.81.0 added limits for custom JavaScript template preprocessors: each call of a preprocessor’s getOptions or
transform function now has a 30-second timeout and a budget of 50 million statements. A very heavy custom template can
hit these limits after upgrading; sites that only use the built-in templates are not affected.
Cross-references
References between pages are written as uid values and resolved during the build, using the uid of every document
being built plus any external cross-reference maps listed in the xref key. The build writes the combined result to
xrefmap.yml in the output folder, so that other sites can link to yours. The first entries of the Pocket Units file are:
### YamlMime:XRefMap
sorted: true
references:
- uid: PocketUnits
name: PocketUnits
href: api/PocketUnits.html
commentId: N:PocketUnits
fullName: PocketUnits
nameWithType: PocketUnits
- uid: PocketUnits.Converter
name: Converter
href: api/PocketUnits.Converter.html
commentId: T:PocketUnits.Converter
fullName: PocketUnits.Converter
nameWithType: Converter
The output folder
The build output goes to the folder named by output (by convention _site). For the modern template the site
contains static HTML for every page, the shared assets and a few machine-readable files:
_site/index.html
_site/api/PocketUnits.html
_site/api/PocketUnits.Converter.html
_site/api/PocketUnits.LengthUnit.html
_site/api/toc.html
_site/api/toc.json
_site/toc.html
_site/toc.json
_site/xrefmap.yml
_site/manifest.json
_site/favicon.ico
_site/logo.svg
_site/public/ (docfx.min.css, docfx.min.js, main.css, main.js, search scripts, bootstrap-icons fonts, Mermaid and KaTeX bundles ...)
-
The
.htmlfiles are the pages. The site is client-only static content, so_sitecan be copied to any static host (GitHub Pages, Azure static hosting, an object-storage bucket). See Deployment. -
toc.jsonfiles hold the navigation of the root table of contents and of each folder with its owntoc.yml. -
xrefmap.ymlis described above. -
manifest.jsonlists every built file with its type, source and output path. Since 2.81.0 it also records the DocFX version that produced it (thedocfx_versionfield, which read 2.81.0 followed by a commit hash in this build). -
The
publicfolder belongs to the modern template. The default template keeps its assets in a different place; the difference matters when you customise styles and is explained in Templates and Customization.
Running docfx docfx.json --serve builds and then hosts _site on http://localhost:8080.
Stage 3: pdf
PDF output is optional and builds on the site produced by the build stage. It applies to DocFX 2.73.0 or newer and to the
modern template. Setting the pdf global metadata to true makes the built site show a "Download PDF" button, and the
docfx pdf command then creates a toc.pdf file for every table of contents in the output directory; pdf: true,
pdfFileName, pdfTocPage and related keys can also be set per table of contents. The details are in
PDF Output. The pdf stage was not executed for this page: it renders
pages in a headless browser, which the Docker image used here does not provide.
Commands and the order they run in
| Command | Reads | Writes |
|---|---|---|
|
.NET projects, solutions, assemblies or source files from |
YAML and a generated |
|
|
the site in |
|
the built site, for tables of contents with |
|
|
everything above |
everything above |
|
a built site folder |
nothing; hosts the folder on a local web server |
Other commands (init, template, download, merge) and the options such as --serve, --warningsAsErrors and
--dryRun are covered in Command-Line Interface.
Pitfalls
-
The build does not call metadata.
docfx buildonly reads the YAML files already in the folder. If you change a comment and run onlybuild, the page does not change; rundocfx docfx.jsonordocfx metadatafirst. -
Generated YAML must be listed in the content setting. The build reads only the files matched by
build.content. In the example the patternapi/**.ymlis what pulls the API pages in; forgetting it produces a site with the articles but no API reference. -
Template order matters. The last entry of
templatewins. Writing[ "modern", "default" ]makes the older template override the newer one. -
Mermaid, math and PDF need the modern template. The official Markdown and PDF pages state this. Check the corresponding pages before relying on a feature with the
defaulttemplate. -
Visual Basic input still produces C# signatures. The VB comments are read, but the rendered syntax is C#.
-
Restore failures stop the metadata stage since 2.80.1, so a project that cannot be restored no longer yields a partial API reference.
-
Treat warnings as errors in CI. The
--warningsAsErrorsoption turns warnings into failures. The builds above printed zero warnings, which is the number to keep at zero.