DotNet API Docs

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 examples were run with the docfx .NET tool inside the mcr.microsoft.com/dotnet/sdk:10.0 Docker image (SDK 10.0.403) against the Pocket Units C# library, and the listings show the real output.

What the .NET API docs feature does

DocFX turns the XML documentation comments of a .NET code base into a browsable API reference. You write the comments in the source as usual (the syntax itself is covered in C# coding conventions and documentation and, for Visual Basic, in Visual Basic XML documentation, analyzers and code style), and DocFX produces one page for each namespace, type and member, with signatures, parameter tables, links between types and links to the Microsoft Learn pages of the framework types you use.

The work happens in two stages that can run on their own:

  • The metadata stage (docfx metadata) reads the .NET input and writes API YAML files into the folder named by metadata.dest. This is the only stage that understands C# and Visual Basic.

  • The build stage (docfx build) turns those YAML files, together with your Markdown articles and tables of contents, into HTML. The docfx command without a verb runs both. The wider pipeline is explained in Basic Concepts and Pipeline.

The official documentation notes that the Docset.Build method does not run the metadata stage; code that drives DocFX from a program calls DotnetApiCatalog.GenerateManagedReferenceYamlFiles first (see the custom filter section below).

The figure follows the Pocket Units converter through both stages. The comment text is Markdown. The metadata stage converts the XML tags such as paramref to HTML but leaves the Markdown in the text untouched, and the build stage renders that Markdown into the page. The member marked with the exclude tag never reaches the YAML.

Documented C# code turned into ManagedReference YAML and then into a rendered API page

The Pocket Units project used on this page

The library is the Pocket Units length converter. Its layout is:

docfx.json
filterConfig.yml
index.md
toc.yml
src/PocketUnits/PocketUnits.csproj
src/PocketUnits/Converter.cs
src/PocketUnits/LengthUnit.cs
src/PocketUnits/Formatting/UnitFormatter.cs
src/PocketUnits/Internal/Cache.cs

The project file targets net8.0 and enables GenerateDocumentationFile. The converter carries the comments that the rest of the page works with: Markdown inside remarks and returns, an example, a seealso, an obsolete member and a member marked with <exclude />:

namespace PocketUnits;

/// <summary>Converts lengths between <see cref="LengthUnit"/> values.</summary>
/// <remarks>
/// The converter is **stateless** and thread safe. See [the NIST guide](https://www.nist.gov/pml/special-publication-811)
/// for the conversion factors.
/// </remarks>
/// <example>
/// Convert ten kilometres to miles:
/// <code language="csharp">
/// double miles = Converter.Convert(10, LengthUnit.Kilometre, LengthUnit.Mile);
/// </code>
/// </example>
public static class Converter
{
    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>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, for example `3.28` feet for one metre.</returns>
    /// <exception cref="ArgumentOutOfRangeException">A unit is not a defined <see cref="LengthUnit"/>.</exception>
    /// <seealso cref="LengthUnit"/>
    public static double Convert(double value, LengthUnit from, LengthUnit to)
        => value * ToMetres(from) / ToMetres(to);

    /// <summary>Old name of <see cref="Convert"/>.</summary>
    [Obsolete("Use Convert instead.")]
    public static double Legacy(double value, LengthUnit from, LengthUnit to) => Convert(value, from, to);

    /// <summary>Internal helper that must not appear in the API reference.</summary>
    /// <exclude />
    public static void Reset() { }
}

The library also has a PocketUnits.Formatting namespace with a UnitFormatter class, a PocketUnits.Internal namespace with a public Cache class that is an implementation detail, and the LengthUnit enum with the members Metre, Kilometre, Foot and Mile in that declaration order.

Choosing the input

The metadata.src array selects what DocFX reads. Each entry is a file mapping: files lists glob patterns, src is the folder they are resolved against (a path relative to docfx.json, which may point outside the folder with ..) and exclude removes matches. Glob patterns themselves cannot walk out of the folder that holds docfx.json, which is why a documentation project that lives next to the library uses src for the hop. DocFX supports four kinds of input, which the official page distinguishes by file extension.

Input File extensions How it is read

Projects and solutions

.csproj, .vbproj, .sln, .slnf, and .slnx (needs .NET 9.0 or later)

An MSBuild design-time build through MSBuildWorkspace. The .NET SDK must be installed and global, with the workloads the projects need.

Source files

.cs, .vb

Compiled with the latest supported .NET SDK on the machine using Microsoft.NET.Sdk. Extra references come from the references property.

Assemblies

.dll, .exe

Reflection over the assembly plus the side-by-side XML documentation file. Independent of the build system and of the language.

Projects

This is the configuration used for the Pocket Units metadata stage. The exclude patterns keep the compiler output folders out of the file search, so that the project is not picked up twice:

{
  "metadata": [
    {
      "src": [
        {
          "src": "src",
          "files": [ "**/*.csproj" ],
          "exclude": [ "**/bin/**", "**/obj/**" ]
        }
      ],
      "dest": "api"
    }
  ]
}

Running docfx metadata docfx.json printed:

Loading project /w/work/src/PocketUnits/PocketUnits.csproj
  Determining projects to restore...
  Restored /w/work/src/PocketUnits/PocketUnits.csproj (in 1.38 sec).
Processing PocketUnits
Creating output...


Build succeeded.

    0 warning(s)
    0 error(s)

The Loading project line is the design-time build. If MSBuild fails to load a project, docfx metadata --logLevel verbose prints the MSBuild log. The official page recommends running dotnet restore before docfx, because restoring is still needed when Visual Studio is not installed even for projects without NuGet dependencies, and DocFX builds the project with the Release configuration unless you set properties.

Solutions

A solution file is accepted in place of the project. The command line below creates a classic .sln and a .slnx for the same project; pointing files at either one gave the same API files (PocketUnits.yml, PocketUnits.Converter.yml, PocketUnits.LengthUnit.yml and the others) as the project input:

dotnet new sln --format slnx -n PocketUnits
dotnet sln PocketUnits.slnx add PocketUnits/PocketUnits.csproj
{
  "metadata": [
    { "src": [ { "src": "src", "files": [ "PocketUnits.slnx" ] } ], "dest": "api" }
  ]
}

The log begins with Loading solution /w/sl/src/PocketUnits.slnx followed by one Loading project line for each project in the solution.

Assemblies

When the library is already built, the assembly route does not need MSBuild at all, which makes it the recommended workaround for project formats DocFX cannot load. The XML documentation file must sit next to the assembly, which GenerateDocumentationFile takes care of (the build produced PocketUnits.dll, PocketUnits.pdb and PocketUnits.xml):

{
  "metadata": [
    {
      "src": [ { "src": "src/PocketUnits/bin/Release", "files": [ "net8.0/PocketUnits.dll" ] } ],
      "dest": "out-dll",
      "references": [ "path-to-reference-assembly.dll" ]
    }
  ]
}

DocFX looks for the reference assemblies in the same folder or in the global system assembly folder; references is only needed when one cannot be found. The example above was run without the references line. The output contained the same pages as the project input, Reset was still hidden by its exclude tag, and the only visible difference was that the items carried no source entries, so there are no View Source links: those come from Source Link information in the .pdb file next to the assembly, which this library does not have.

The F# limitation

F# projects are not supported as project input; the official page names .fsproj as an unsupported project format and recommends generating the documentation from the compiled assemblies. This is the output of the metadata stage for a default F# class library created with dotnet new classlib -lang "F#":

warning: Skip unsupported file /w/fs2/src/PocketUnitsFs/PocketUnitsFs.fsproj
warning: No .NET API project detected.
Creating output...
warning: No .NET API detected. Check the input files and earlier load or compilation diagnostics.


Build succeeded with warning.

    3 warning(s)
    0 error(s)

Nothing is written to the output folder. The documentation lists no other F# route, so point files at the .dll instead and keep the XML documentation file next to it.

The restore step, the Release configuration and MSBuild properties

For project and solution input DocFX runs dotnet restore first. Since 2.80.1 it stops the metadata stage when the restore fails. Set noRestore to true to skip the step when the projects are already restored, for example because a previous CI step did it. This is what happens if you set it and the project has never been restored:

Loading project /w/nr/src/PocketUnits/PocketUnits.csproj
error: .../PocketUnits.AssemblyInfo.cs(13,12): error CS0246: The type or namespace name 'System' could not be found (are you missing a using directive or an assembly reference?)
error: .../PocketUnits.AssemblyInfo.cs(17,55): error CS0518: Predefined type 'System.String' is not defined or imported
Creating output...
warning: No .NET API detected. Check the input files and earlier load or compilation diagnostics.


Build failed.

    1 warning(s)
    20 error(s)

DocFX exited with code 255 and wrote no files. After dotnet restore in the project folder the same configuration, with "noRestore": true, succeeded. A missing restore therefore does not say "run restore"; the compiler errors about System are the signature of the problem.

properties passes MSBuild properties, like the /property:name=value option of dotnet build. The most common use is TargetFramework. A project that targets several frameworks is built once for each of them, and the official page suggests naming one to make the stage faster:

{
  "metadata": [
    {
      "src": [ { "src": "src", "files": [ "**/*.csproj" ] } ],
      "dest": "api",
      "properties": { "TargetFramework": "net8.0" }
    }
  ]
}

The page does not say that a multi-targeted project fails without it. A copy of Pocket Units with <TargetFrameworks>net8.0;net10.0</TargetFrameworks> produced the API files both with and without the property. Two rules of the reference are worth keeping in mind. For source-file input only DefineConstants and AllowUnsafeBlocks are honoured and any other property is ignored without a message. And references is used for assemblies and source files, not for solution or project input, which take their references from the project.

SDK compatibility

DocFX publishes an SDK compatibility report that is refreshed by a nightly workflow of its repository. It records which .NET SDK and project target framework combinations the tool was able to read, and its authors state that the results are automated observations and not a support guarantee; a combination that is not in the matrix has no claim either way. The configured matrix, as described in the maintenance guide of the report, has five rows of SDK and project target:

SDK installed (latest of the channel) Project target framework

8.0

net8.0

9.0

net9.0

10.0

net10.0

11.0

net10.0

11.0

net11.0

Each row runs two scenarios: a plain C# class (its public type and method must appear in the metadata) and a Razor component with @inherits and a code-behind override. Each row is run against the released DocFX tool (target net10.0) and against the nightly tools built for net8.0, net9.0, net10.0 and net11.0, on Linux only. The columns of the report show the DocFX version and the tool target framework, not the target of your project. The report is a good first stop when a newer SDK fails to read a project, because source generators shipped with an SDK can need a newer Roslyn compiler than the one bundled with an older tool target.

DocFX 2.81.0 adds a .NET 11 release-candidate target and keeps the 8, 9 and 10 targets. In the scratch environment of this page, the 2.81.0 tool read a net8.0 project and a net10.0 project with the SDK 10.0.403 without problems.

Comments are Markdown

DocFX treats the text inside the XML tags as Markdown. The Pocket Units comments use **stateless** and an inline link in remarks, and backticks in returns. After the metadata stage the Markdown is still in the YAML, converted only where an XML tag was involved (the see and paramref tags become xref and code elements):

  summary: Converts <code class="paramref">value</code> from one unit to another.
  syntax:
    return:
      type: System.Double
      description: The converted magnitude, for example `3.28` feet for one metre.

The build stage renders them, so the page contains <strong>stateless</strong>, an anchor for the NIST link and <code>3.28</code>. The same comments appear in the IntelliSense window of an editor, which does not render Markdown, so heavy formatting there looks like noise. Setting shouldSkipMarkup to true makes DocFX leave the text alone. The YAML then records shouldSkipMarkup: true, and the build leaves the characters as written: the built page showed **stateless** and the backticks literally.

{
  "metadata": [
    {
      "src": [ { "src": "src", "files": [ "**/*.csproj" ] } ],
      "dest": "api",
      "shouldSkipMarkup": true
    }
  ]
}

DocFX supports the recommended XML tags of the C# language reference. The ones used above produce these parts of the rendered page: summary the description under the heading, param the Parameters list, returns the Returns list, exception the Exceptions list, seealso the See Also list, example the Examples section and remarks the Remarks section. The example tag has to be a sibling of remarks. When the example was nested inside remarks, the built page showed it as a raw example element inside the Remarks text instead of an Examples section. codeSourceBasePath is the base folder for <code source="Example.cs"/> references, which pull code for examples from a file instead of the comment.

Hiding APIs

By default DocFX documents only the types and members that code in another assembly can call. It also applies a built-in set of filter rules, in defaultfilterconfig.yml in the DocFX repository, which hide common patterns such as members marked with EditorBrowsableAttribute. There are several ways to hide more, from the narrowest to the broadest.

The exclude tag

Put <exclude /> in the comment of a type or member to drop that one API. This is the tag on Reset:

/// <summary>Internal helper that must not appear in the API reference.</summary>
/// <exclude />
public static void Reset() { }

The metadata stage accepted it for project and for assembly input, and Reset appears neither in the YAML nor in the pages. It also stays hidden when includePrivateMembers is on.

Filter rules in a YAML file

metadata.filter points to a YAML file with an apiRules list. The rules are tried in order and processing stops at the first rule that matches an API. An API that matches no rule is included under the default filter. A rule is an include or an exclude and carries one or more of these conditions:

Condition Meaning

uidRegex

A regular expression tested against the UID of the API. Every API has a UID, the unique identifier DocFX computes (for example PocketUnits.Converter.Convert(System.Double,PocketUnits.LengthUnit,PocketUnits.LengthUnit)).

type

The kind of API: Namespace, Class, Struct, Enum, Interface, Delegate, Event, Field, Method or Property; Type stands for a class, struct, enum, interface or delegate, and Member for a field, event, method or property.

hasAttribute

Matches APIs that carry an attribute. uid names the attribute class, ctorArguments lists the positional constructor arguments to match and ctorNamedArguments the named ones.

Filtering is hierarchical. Excluding a namespace removes the types and members inside it, and excluding a type removes its members. The Pocket Units filter file hides the Internal namespace and every member marked [Obsolete]:

filterConfig.yml
apiRules:
- exclude:
    uidRegex: ^PocketUnits\.Internal
    type: Namespace
- exclude:
    hasAttribute:
      uid: System.ObsoleteAttribute
      type: Member

It is referenced from docfx.json with "filter": "filterConfig.yml". With it, PocketUnits.Internal.yml and PocketUnits.Internal.Cache.yml are not written, and neither Legacy nor Reset is in PocketUnits.Converter.yml. Without the hasAttribute rule the YAML of Legacy lists System.ObsoleteAttribute and the declaration shows [Obsolete("Use Convert instead.")].

Because the first matching rule wins, an include rule placed before a broader exclude carves an exception out of it. The official example hides everything under Microsoft.DevDiv except Microsoft.DevDiv.SpecialCase:

apiRules:
- include:
    uidRegex: ^Microsoft\.DevDiv\.SpecialCase
- exclude:
    uidRegex: ^Microsoft\.DevDiv

Switches that widen or narrow the default

  • disableDefaultFilter turns the built-in rules off.

  • includePrivateMembers adds private and internal APIs. The declarations then show the private and internal keywords. With it enabled, the Pocket Units page gained ToMetres(LengthUnit) with the signature private static double ToMetres(LengthUnit unit).

  • includeExplicitInterfaceImplementations adds explicit interface implementations. Both switches default to false.

  • allowCompilationErrors lets the stage go on when the code does not compile.

The warning "No .NET API detected"

This warning does not tell you the cause. It means that no metadata was produced, because the input did not load, the globs matched nothing useful, or everything was filtered out. Look at the diagnostics before it, check that metadata.src matches the files you intended, and review visibility, the default filter, <exclude /> tags and any custom filter file (the warning names the file when one is configured).

A custom filter written in code

When rules in YAML are not enough, a program can call the metadata stage itself and supply two callbacks. IncludeApi decides about a symbol and IncludeAttribute about an attribute shown on a symbol. Each returns a SymbolIncludeState: Include, Exclude or Default. The callbacks run before the default rules and after the accessibility rules, so private members cannot be switched on unless includePrivateMembers is true. Hiding a symbol hides everything it contains. This console project, which references the Docfx.Dotnet package, hides every API whose name ends in Cache and the Obsolete attribute:

Gen.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Docfx.Dotnet" Version="2.81.0" />
    <PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="5.9.0" />
  </ItemGroup>
</Project>
Program.cs
using Docfx.Dotnet;
using Microsoft.CodeAnalysis;

var options = new DotnetApiOptions
{
    // Hide every API whose name ends in "Cache".
    IncludeApi = symbol => symbol.Name.EndsWith("Cache")
        ? SymbolIncludeState.Exclude
        : SymbolIncludeState.Default,

    // Hide the Obsolete attribute from declarations.
    IncludeAttribute = symbol => symbol.ToDisplayString() == "System.ObsoleteAttribute"
        ? SymbolIncludeState.Exclude
        : SymbolIncludeState.Default
};

await DotnetApiCatalog.GenerateManagedReferenceYamlFiles(args[0], options);

Run with dotnet Gen.dll docfx.json, it wrote the API files without Cache and without the [Obsolete(...)] text in the Legacy declaration; the method itself stayed in the reference, because IncludeAttribute hides attributes and not members. The explicit Microsoft.CodeAnalysis.Workspaces.MSBuild reference is not in the official snippet. Without it, the first run failed with The build host could not be found at '.../BuildHost-netcore/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll', because the build host folder was not copied to the output folder of the console project.

Layout of the generated reference

Three options of the metadata section decide how the API is organized in the table of contents and in the pages.

Option Values Effect

namespaceLayout

flattened (default), nested

Whether namespaces are listed side by side in toc.yml or in a tree following the dots of their names.

memberLayout

samePage (default), separatePages

Whether members are sections of the page of their type, or have a page of their own.

categoryLayout

flattened (default), nested, none

How the categories (classes, enums, and so on) are shown. It only applies to the apiPage and markdown output formats, because mref has no categories.

enumSortOrder

alphabetic (default), declaringOrder

The order of the members of an enum.

With the defaults the generated toc.yml of Pocket Units lists PocketUnits and PocketUnits.Formatting as two siblings, ends with memberLayout: SamePage, and the enum members come out as Foot, Kilometre, Metre and Mile. With "namespaceLayout": "nested", "memberLayout": "separatePages" and "enumSortOrder": "declaringOrder" the same file becomes:

### YamlMime:TableOfContent
items:
- uid: PocketUnits
  name: PocketUnits
  type: Namespace
  items:
  - uid: PocketUnits.Formatting
    name: Formatting
    type: Namespace
    items:
    - uid: PocketUnits.Formatting.UnitFormatter
      name: UnitFormatter
      type: Class
  - uid: PocketUnits.Converter
    name: Converter
    type: Class
  - uid: PocketUnits.LengthUnit
    name: LengthUnit
    type: Enum
memberLayout: SeparatePages

The enum members are Metre, Kilometre, Foot and Mile, and the build wrote one page for each member, for example _site/api/PocketUnits.Converter.Convert.html and _site/api/PocketUnits.Formatting.UnitFormatter.Format.html, next to the type pages. Nested layout is only visible when the namespaces share a prefix. The global namespace has no name; set globalNamespaceId if you want one.

Output formats

outputFormat selects what the metadata stage writes. The default mref writes ManagedReference YAML, which the template turns into pages. The other two are meant for custom pipelines and newer templates:

Value Result in the Pocket Units run

mref

PocketUnits.Converter.yml and friends, starting with ### YamlMime:ManagedReference. The default.

apiPage

The same file names, but the content is ### YamlMime:ApiPage: a title, and a body list of typed blocks such as facts, markdown, code, h2 and parameters. Its toc.yml has href entries and category labels (Classes, Enums). The enum fields came out alphabetically.

markdown

One CommonMark file for each namespace and type (PocketUnits.Converter.md, and so on), with a toc.yml. Method signatures use fenced code blocks.

The apiPage and markdown files were fed to docfx build with the modern template and produced HTML pages for every namespace and type. When the output is markdown, files listed in build.content must include *.md as well as the YAML. The modern template is the one these formats are aimed at; the page on Templates and Customization compares it with default.

The View Source link on each page depends on Git information. disableGitFeatures removes the links. For assemblies, the link comes from Source Link data in the .pdb. Version 2.81.0 added sourceLinkExclude, a list of glob patterns matched against source document paths (for a DLL the path recorded in the PDB, which may come from another machine) to hide the link of selected documents, for example generated files, without removing the APIs. It defaults to an empty list, matching is case-insensitive and any matching pattern excludes the document. The reference shows **/ExampleGenerator/** to hide the links of all files from a source generator. This option was not exercised here because the sample has no Source Link mappings.

The whole Pocket Units configuration

Putting the choices together, this is the docfx.json used for the final run. The build section reads the generated YAML, the table of contents and the Markdown articles, and uses both templates:

docfx.json
{
  "metadata": [
    {
      "src": [
        {
          "src": "src",
          "files": [ "**/*.csproj" ],
          "exclude": [ "**/bin/**", "**/obj/**" ]
        }
      ],
      "dest": "api",
      "filter": "filterConfig.yml",
      "namespaceLayout": "nested",
      "memberLayout": "separatePages",
      "enumSortOrder": "declaringOrder"
    }
  ],
  "build": {
    "content": [ { "files": [ "api/**.yml", "toc.yml", "*.md" ] } ],
    "output": "_site",
    "template": [ "default", "modern" ]
  }
}

Running docfx docfx.json ended with:

Building 5 file(s) in ManagedReferenceDocumentProcessor(BuildManagedReferenceDocument=>SplitClassPageToMemberLevel=>ValidateManagedReferenceDocumentMetadata=>ApplyOverwriteDocumentForMref=>FillReferenceInformation=>FillMetadata)...
Applying templates to 10 model(s)...
XRef map exported.


Build succeeded.

    0 warning(s)
    0 error(s)

The generated page for Convert has a Parameters list (value, from and to, with the types linked to LengthUnit or to learn.microsoft.com for double), a Returns list, an Exceptions list and a See Also list, all taken from the comment. The toc.yml in the root (a link to the api/ folder) and the other TOC keys are covered in Table of Contents. How <see cref="LengthUnit"/> becomes a link, and how to link to the framework or to other sites, is in Links and Cross References. The meaning of every other key of the file is in docfx.json.

Pitfalls

  • Stale output. The metadata stage writes files into dest and does not delete the ones it no longer produces. After adding the Pocket Units filter to a folder that already held unfiltered output, PocketUnits.Internal.yml and PocketUnits.Internal.Cache.yml were still there (the new toc.yml no longer listed them), and a build that reads api/**.yml would still render them. Delete the dest folder when you change a filter.

  • An example tag nested in remarks is not rendered as an Examples section. Make it a sibling.

  • noRestore: true on a project that was never restored produces compiler errors about System, not a restore message, and a process exit code of 255.

  • A solution or project that MSBuild cannot load gives a vague "No .NET API detected" warning. Check the earlier lines of the log and use --logLevel verbose to see the MSBuild log.

  • A .fsproj is skipped with a warning; use the compiled assembly instead.

  • The glob patterns in files cannot go above the folder that holds docfx.json. Use src for that.

  • Markdown in comments is also shown by editors that do not render Markdown; use shouldSkipMarkup or keep the formatting simple.

  • A filter rule that contains ^ or $ is a regular expression, so a dot in a UID must be written \..

  • categoryLayout has no effect on the default mref output.