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

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.

flowchart LR SRC["C# / VB projects
.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() { }
}

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" ]
  }
}

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

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 uid is 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 an xref element. 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)
  • TocDocumentProcessor handled the two toc.yml files (the hand-written one and the generated api/toc.yml).

  • ManagedReferenceDocumentProcessor handled the three API YAML files. Its steps show where overwrite files are applied (ApplyOverwriteDocumentForMref) and where references are filled in.

  • ConceptualDocumentProcessor handled 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" ]
  }
}

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 .html files are the pages. The site is client-only static content, so _site can be copied to any static host (GitHub Pages, Azure static hosting, an object-storage bucket). See Deployment.

  • toc.json files hold the navigation of the root table of contents and of each folder with its own toc.yml.

  • xrefmap.yml is described above.

  • manifest.json lists every built file with its type, source and output path. Since 2.81.0 it also records the DocFX version that produced it (the docfx_version field, which read 2.81.0 followed by a commit hash in this build).

  • The public folder 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

docfx metadata docfx.json

.NET projects, solutions, assemblies or source files from metadata.src

YAML and a generated toc.yml in dest

docfx build docfx.json

build.content files (the YAML, Markdown and toc.yml files), templates

the site in output

docfx pdf docfx.json

the built site, for tables of contents with pdf: true

toc.pdf files in the output folder

docfx docfx.json

everything above

everything above

docfx serve _site

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 build only reads the YAML files already in the folder. If you change a comment and run only build, the page does not change; run docfx docfx.json or docfx metadata first.

  • Generated YAML must be listed in the content setting. The build reads only the files matched by build.content. In the example the pattern api/**.yml is 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 template wins. 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 default template.

  • 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 --warningsAsErrors option turns warnings into failures. The builds above printed zero warnings, which is the number to keep at zero.