| Latest Version | Gallery | Issues | Testing | License | Discord |
|---|---|---|---|---|---|
Xml2Doc converts C# compiler XML documentation into deterministic, linkable Markdown. It is available as a library, CLI, and MSBuild package.
2.4.0 adds deterministic per-document metadata, caller-supplied metadata, and authoritative multi-document output layouts across Core, CLI, and MSBuild.
Highlights:
- Templates and programmatic front-matter providers receive immutable document identity, namespace, symbol, kind, and resolved output-path metadata.
- Core, CLI configuration, repeated CLI arguments, and MSBuild share one deterministic caller-metadata representation.
- Authoritative
DocumentPlanandIDocumentPathResolverabstractions keep paths, links, plans, reports, manifests, pruning, and writes aligned. - The default
flatlayout remains compatible; the opt-innamespace-folderslayout organizes type and namespace pages hierarchically. - Unsafe, non-canonical, or case-insensitively colliding paths fail before output is written with
XML2DOC008orXML2DOC009. - Existing aggregation, structured diagnostics, dry-run/diff/reporting, bounded parallel rendering, incremental writes, templates, front matter, auto-linking, alias maps, and external-documentation fallback remain available.
Existing single-project behavior remains compatible. Xml2Doc_GenerateIndex=false is still supported when independent project invocations intentionally share an output directory.
| Component | Target frameworks | Purpose |
|---|---|---|
Xml2Doc.Core |
netstandard2.0, net8.0, net9.0 |
Library and renderer |
Xml2Doc.Cli |
net8.0, net9.0 |
Command-line host |
Xml2Doc.MSBuild |
net472, net8.0 |
Visual Studio/MSBuild.exe and dotnet build task hosts |
The MSBuild package selects its task assembly automatically. Do not define a custom GenerateMarkdownFromXmlDoc target or manually call Xml2Doc's internal fingerprint targets.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Xml2Doc.MSBuild" Version="2.4.0" PrivateAssets="all" />
</ItemGroup>
</Project>GenerateDocumentationFile must be true for projects whose compiler XML is used as documentation input. The package imports its own generation and incremental-build targets.
Most Xml2Doc properties can be placed in either a project file or a solution-level Directory.Build.props. Project properties override values imported earlier from Directory.Build.props.
The package currently assigns Xml2Doc_Toc, Xml2Doc_NamespaceIndex, and Xml2Doc_BasenameOnly to false in its imported defaults. Set those three in the project file or Directory.Build.targets when enabling them globally.
| Property | Default | Behavior |
|---|---|---|
GenerateDocumentationFile |
SDK-dependent | Compiler property. Set true for every project that contributes XML documentation. |
Xml2Doc_Enabled |
true |
Enables normal per-project Markdown generation. |
Xml2Doc_SingleFile |
false |
false writes one file per type; true writes one combined file. |
Xml2Doc_OutputDir |
$(MSBuildProjectDirectory)\docs |
Output root for per-type mode. |
Xml2Doc_OutputFile |
$(MSBuildProjectDirectory)\docs\api.md |
Output path for single-file mode. |
Xml2Doc_GenerateIndex |
true |
Writes index.md in per-type mode. |
Xml2Doc_FileNameMode |
clean |
clean or verbatim. |
Xml2Doc_RootNamespaceToTrim |
Empty | Namespace prefix trimmed from displayed type names. |
Xml2Doc_TrimRootNamespaceInFileNames |
false |
Also trims the configured root namespace from filenames. |
Xml2Doc_CodeBlockLanguage |
csharp |
Fenced-code language. |
Xml2Doc_AnchorAlgorithm |
default |
default, github, gfm, or kramdown. |
Xml2Doc_Toc |
false |
Emits member tables of contents where supported. |
Xml2Doc_NamespaceIndex |
false |
Emits namespace index output in per-type mode. |
Xml2Doc_BasenameOnly |
false |
Uses only basename segments for generated type files and links. |
Xml2Doc_ParallelDegree |
1 |
Maximum concurrent per-type renders. |
Xml2Doc_LineEndings |
lf |
lf, crlf, or native. lf is deterministic across hosts. |
Xml2Doc_MetadataFile |
Empty | JSON object containing generic scalar/list caller metadata. |
Xml2Doc_Layout |
flat |
Multi-document layout: flat or namespace-folders. |
Xml2Doc_PruneStaleFiles |
false |
Removes stale files previously owned by the same invocation. Per-type mode only. |
Xml2Doc_ManifestIdentity |
Empty | Stable identity required when pruning is enabled. |
Xml2Doc_ReportPath |
$(Xml2Doc_OutputDir)\xml2doc-report.json |
JSON report path for normal project generation. |
Xml2Doc_ReportIncludeTimestamp |
false |
Adds a timestamp to reports when enabled. |
Xml2Doc_DryRun |
false |
Plans generation without writing Markdown. |
Xml2Doc_Diff |
false |
Reserved in the MSBuild task; currently has no effect. |
Xml2Doc_Dump |
false |
Logs evaluated paths and key generation inputs. |
Xml2Doc_LogChosenTask |
false |
Logs the selected task TFM and assembly path. |
Stale-file pruning is supported only in per-type mode. Single-file mode replaces its configured output directly.
Xml2Doc automatically loads XML documentation found beside referenced project assemblies for <inheritdoc /> resolution. Referenced members participate in inheritance lookup but do not generate additional Markdown pages.
Extra reference XML can be supplied explicitly:
<ItemGroup>
<Xml2Doc_ReferenceXml Include="$(BaseOutputPath)Contracts\Contracts.xml" />
</ItemGroup>Missing explicit reference files and unresolved <inheritdoc /> targets produce warnings. Reference XML contents participate in incremental fingerprinting.
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Xml2Doc_Enabled>true</Xml2Doc_Enabled>
<Xml2Doc_SingleFile>false</Xml2Doc_SingleFile>
<Xml2Doc_OutputDir>$(MSBuildProjectDirectory)\docs</Xml2Doc_OutputDir>
<Xml2Doc_FileNameMode>clean</Xml2Doc_FileNameMode>
<Xml2Doc_RootNamespaceToTrim>MyCompany.MyProduct</Xml2Doc_RootNamespaceToTrim>
<Xml2Doc_TrimRootNamespaceInFileNames>true</Xml2Doc_TrimRootNamespaceInFileNames>
<Xml2Doc_PruneStaleFiles>true</Xml2Doc_PruneStaleFiles>
<Xml2Doc_ManifestIdentity>$(MSBuildProjectName)</Xml2Doc_ManifestIdentity>
<Xml2Doc_ParallelDegree>4</Xml2Doc_ParallelDegree>
<Xml2Doc_LineEndings>lf</Xml2Doc_LineEndings>
</PropertyGroup>Use one small project as the aggregation owner. Participating projects emit compiler XML; the owner performs the Markdown render once.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<!-- The owner orchestrates documentation; it does not render its own compiler XML. -->
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<Xml2Doc_Enabled>false</Xml2Doc_Enabled>
<Xml2Doc_AggregateEnabled>true</Xml2Doc_AggregateEnabled>
<Xml2Doc_OutputDir>$(MSBuildProjectDirectory)\..\docs\api</Xml2Doc_OutputDir>
<Xml2Doc_FileNameMode>clean</Xml2Doc_FileNameMode>
<Xml2Doc_LineEndings>lf</Xml2Doc_LineEndings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\src\ProjectA\ProjectA.csproj" />
<ProjectReference Include="..\src\ProjectB\ProjectB.csproj" />
<PackageReference Include="Xml2Doc.MSBuild" Version="2.4.0" PrivateAssets="all" />
</ItemGroup>
</Project>Each participating project must emit XML documentation:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>If a participant also uses Xml2Doc and points at the aggregate output directory, either disable its normal Markdown generation:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Xml2Doc_Enabled>false</Xml2Doc_Enabled>
</PropertyGroup>or delegate only index ownership to the aggregate owner:
<PropertyGroup>
<Xml2Doc_GenerateIndex>false</Xml2Doc_GenerateIndex>
</PropertyGroup>The owner validates referenced Xml2Doc projects before normal referenced-project builds begin. A participant that still claims the same index.md fails with XML2DOC007.
Additional primary XML inputs that are not project references can be supplied with Xml2Doc_AggregateXml:
<ItemGroup>
<Xml2Doc_AggregateXml Include="$(RepoRoot)\artifacts\External.Contracts.xml" />
</ItemGroup>Primary aggregate inputs generate pages. Xml2Doc_ReferenceXml remains separate and is used only for inheritance/reference resolution.
| Property | Default | Behavior |
|---|---|---|
Xml2Doc_AggregateEnabled |
false |
Makes the project the repository aggregation owner. |
Xml2Doc_AggregateValidateIndexOwnership |
true |
Checks referenced Xml2Doc projects for conflicting index.md ownership. |
Xml2Doc_AggregateReportPath |
$(Xml2Doc_OutputDir)\xml2doc-aggregate-report.json |
Aggregate JSON report path. |
Xml2Doc_AggregateOutputStamp |
$(IntermediateOutputPath)xml2doc.aggregate.stamp |
Aggregate incremental-build stamp. |
Xml2Doc_AggregateFingerprintFile |
$(IntermediateOutputPath)xml2doc.aggregate.fingerprint.txt |
Aggregate input/options fingerprint. |
Xml2Doc_AggregateOutputLedger |
$(IntermediateOutputPath)xml2doc.aggregate.outputs.txt |
Recorded aggregate output paths. |
The aggregate owner reuses normal renderer settings such as output mode, filename mode, namespace trimming, index generation, line endings, pruning, and reporting timestamp behavior. Aggregate input identities, explicit reference XML identities, rendering options, and host-native newline policy participate in the aggregate fingerprint.
Build the owner normally:
dotnet build .\docs\ApiDocs.csproj -c ReleaseThe aggregate report includes canonical xmlInputs. Serial and parallel repository builds are expected to produce the same file set and bytes.
For a focused explanation of this pattern, see docs/msbuild-repository-aggregation.md.
Repositories do not have to adopt an aggregation owner immediately. Independent projects may continue to share a directory if each has a distinct manifest identity and does not attempt to generate a shared index:
<PropertyGroup>
<Xml2Doc_OutputDir>$(MSBuildThisFileDirectory)docs</Xml2Doc_OutputDir>
<Xml2Doc_GenerateIndex>false</Xml2Doc_GenerateIndex>
<Xml2Doc_PruneStaleFiles>true</Xml2Doc_PruneStaleFiles>
<Xml2Doc_ManifestIdentity>$(MSBuildProjectName)</Xml2Doc_ManifestIdentity>
<Xml2Doc_ReportPath>$(Xml2Doc_OutputDir)\xml2doc-report-$(MSBuildProjectName).json</Xml2Doc_ReportPath>
</PropertyGroup>This prevents index races but intentionally does not create a unified repository index. Prefer the aggregation-owner pattern when one complete index is required.
Install the .NET tool:
dotnet tool install --global Xml2Doc.Cli --version 2.4.0Generate per-type documentation from one XML file:
xml2doc `
--xml .\bin\Release\net9.0\MyLibrary.xml `
--out .\docs `
--file-names clean `
--rootns MyCompany.MyProduct `
--parallel 4 `
--line-endings lfAggregate multiple projects directly from the CLI by repeating --xml:
xml2doc `
--xml .\src\ProjectA\bin\Release\net9.0\ProjectA.xml `
--xml .\src\ProjectB\bin\Release\net9.0\ProjectB.xml `
--out .\docs `
--file-names clean `
--line-endings lfOne XML input uses the compatible single-input loading path. Two or more primary inputs use deterministic Core aggregation.
| Option | Behavior |
|---|---|
--xml <path> |
Primary XML input. Repeat to aggregate multiple inputs. |
--out <path> |
Output directory, or output file with --single. |
--single |
Generate one combined Markdown file. |
--file-names <verbatim|clean> |
Filename mode. |
--rootns <namespace> |
Trim a root namespace from display names. |
--trim-rootns-filenames |
Also trim the root namespace from filenames. |
--lang <language> |
Fenced-code language. |
--anchor-algorithm <mode> |
default, github, gfm, or kramdown. |
--template <path> |
Apply a file-based template. |
--front-matter <path> |
Prepend configured front matter. |
--metadata <key=value> |
Add generic caller metadata. Repeat for multiple values. |
--auto-link |
Enable safe free-text symbol linking. |
--alias-map <path> |
Load an additional alias map. |
--external-docs <base-url> |
Route unresolved references to external documentation. |
--toc |
Emit member tables of contents. Directory output only. |
--namespace-index |
Emit namespace index/pages. Directory output only. |
--no-index |
Suppress per-type index.md. |
--basename-only |
Use basename-only output names and links. |
--parallel <N> |
Maximum per-type render parallelism. |
--prune-stale |
Remove stale output owned by the selected manifest identity. Directory output only. |
--manifest-id <identity> |
Stable ownership identity used with pruning. |
--line-endings <style> |
lf, crlf, or native. |
--layout <mode> |
Multi-document layout: flat or namespace-folders. |
--report <path> |
Write a JSON execution report. |
--dry-run |
Plan without writing output. |
--diff |
Compare generated output with current files without modifying them. |
--config <path> |
Load JSON configuration. CLI arguments take precedence. |
--help, -h |
Show help. |
--dry-run and --diff are mutually exclusive. Diff returns exit code 0 when output is current and 3 when differences are found. Invalid CLI arguments return 1; diagnostic/runtime errors return 2.
{
"XmlInputs": [
"src/ProjectA/bin/Release/net9.0/ProjectA.xml",
"src/ProjectB/bin/Release/net9.0/ProjectB.xml"
],
"Out": "docs",
"FileNames": "clean",
"GenerateIndex": true,
"Parallel": 4,
"Metadata": {
"package": "MyCompany.MyProduct",
"tags": ["api", "stable"],
"version": "2.4.0"
},
"LineEndings": "lf",
"Layout": "namespace-folders"
}Repeated CLI --metadata key=value arguments override matching keys from the JSON Metadata
object. MSBuild uses the same value model through Xml2Doc_MetadataFile. Metadata values may be
null, strings, booleans, numbers, dates, enums, or lists of supported scalar values.
XmlInputs takes precedence over the legacy single-input Xml property when no --xml arguments are supplied. Repeated CLI --xml arguments take precedence over both JSON properties.
When a CLI report is enabled for aggregate input, it includes canonical xmlInputs while retaining the compatible xml field for the first canonical input.
Xml2Doc uses stable diagnostic identifiers:
| ID | Meaning |
|---|---|
XML2DOC001 |
Unresolved cref. |
XML2DOC002 |
Duplicate generated anchor. |
XML2DOC003 |
Malformed XML documentation input. |
XML2DOC004 |
Documented symbol is missing a summary. |
XML2DOC005 |
Unresolved <inheritdoc /> target. |
XML2DOC006 |
Multiple primary XML inputs define the same documentation member. |
XML2DOC007 |
Multiple MSBuild projects claim the same generated aggregate index. |
XML2DOC008 |
A document path resolver returned an unsafe or non-canonical path. |
XML2DOC009 |
Multiple documents resolve to the same case-insensitive logical path. |
CLI diagnostics use the stable format xml2doc <severity> <code>: <message>. Warnings do not fail generation; diagnostic errors return exit code 2. MSBuild maps diagnostics to normal warning/error logging.
When stale pruning is enabled, Xml2Doc records output ownership under the output root:
<output-root>/.xml2doc/
├── manifests/
│ └── <identity-sha256>.json
└── transactions/
Commit .xml2doc/manifests when generated Markdown is versioned and ownership must survive a clean checkout. Ignore .xml2doc/transactions; it is local best-effort staging state.
Normal MSBuild generation keeps its stamp, fingerprint, and output ledger under IntermediateOutputPath. Repository aggregation uses separate xml2doc.aggregate.* lifecycle files so one aggregate owner has one independent incremental state boundary.
Generated Markdown uses LF on every platform by default. Use native only when host-specific bytes are intentional.
Core consumers can replace individual rendering services through RendererOptions. Built-in extension points include:
IAnchorGeneratorIAliasProviderITemplateRendererIAutoLinkerIDocumentPathResolverIExternalSymbolResolverISignatureRendererSignatureStyle
The CLI exposes the applicable built-in behaviors through flags and JSON configuration. Consumer-provided service implementations remain a direct Xml2Doc.Core integration concern.
Templates and programmatic front-matter providers receive the same TemplateRenderContext. Contexts
created by the renderer expose an immutable DocumentDescriptor through context.Document, with:
- a stable document identity;
- the existing
TemplateDocumentKind; - namespace and unqualified symbol metadata where applicable;
- the resolved output-root-relative logical path through
context.OutputPath.
Type pages use their complete XML documentation ID, such as T:Temp.Widget. Namespace pages use
N:<namespace>, while the primary index, namespace overview, and consolidated document use
xml2doc:index, xml2doc:namespaces, and xml2doc:single-file respectively. Logical paths always
use forward slashes. RenderToString() exposes no output path because it has no resolved output
location.
Compiler XML does not identify whether a documented type is a class, interface, record, struct, or
enum, so Xml2Doc does not infer that metadata. Existing direct construction and deconstruction of
TemplateRenderContext(Content, Title, Kind) remains available.
File templates expose the same metadata through {{documentId}}, {{namespace}}, {{symbol}}, and
{{outputPath}}. Tokens without an applicable value render as an empty string.
Caller-supplied metadata is provided programmatically through RendererOptions.Metadata, through
repeated CLI --metadata key=value arguments or the JSON Metadata object, and through the
MSBuild Xml2Doc_MetadataFile property. Core snapshots and validates the values before rendering,
then exposes the immutable merged collection through TemplateRenderContext.Metadata and emits it
as deterministic YAML front matter.
Generic caller keys have the lowest precedence. Programmatic per-document front matter can override
them, while Core-derived documentId, documentKind, namespace, symbol, and outputPath values
win collisions whenever caller metadata participates in the merge. Without caller metadata,
existing programmatic front-matter output remains unchanged. The existing literal
FrontMatterPath/--front-matter mode remains unchanged and cannot be combined with caller metadata.
Multi-document output is planned once in Core before rendering. The default flat layout preserves
the existing type, index, and namespace paths. The opt-in namespace-folders layout places type
pages and namespace indexes beneath namespaces/<namespace>/, while keeping the primary index and
namespace overview at the output root. Links, templates, dry-run plans, reports, ownership
manifests, pruning, and physical writes all consume this same authoritative plan.
Core callers can supply an IDocumentPathResolver through
RendererOptions.DocumentPathResolver; RendererOptions.Layout selects a built-in resolver when
no custom resolver is supplied. Resolvers return canonical output-root-relative paths using /.
Rooted paths, traversal, backslashes, empty segments, and case-insensitive collisions are rejected
before any output or manifest is written. CLI and MSBuild expose only the named built-in layouts.
Single-file output is unaffected.
Use a single repository aggregation owner with Xml2Doc_AggregateEnabled=true. If a referenced project still targets the same output and owns its index, Xml2Doc fails with XML2DOC007. Disable that project's normal generation or set Xml2Doc_GenerateIndex=false.
Ensure every participating project has:
<GenerateDocumentationFile>true</GenerateDocumentationFile>The aggregate owner fails rather than silently producing an incomplete index.
XML2DOC006 means two primary XML inputs document the same member ID. Remove the duplicate primary input or correct the project/input boundary. Xml2Doc fails deterministically instead of selecting an owner based on input order.
Use per-type mode with pruning enabled and a stable manifest identity. The first run establishes ownership; files predating the manifest require one manual cleanup.
Use a current Xml2Doc.MSBuild package. Task runtime dependencies are packaged beside each task assembly for supported hosts.
<PropertyGroup>
<Xml2Doc_Dump>true</Xml2Doc_Dump>
<Xml2Doc_LogChosenTask>true</Xml2Doc_LogChosenTask>
</PropertyGroup>2.0.3— documentation and lifecycle correctness.2.1.0— rendering extensibility.2.2.0— structured diagnostics and runner/pipeline completion.2.3.0— deterministic multi-project aggregation across Core, CLI, and MSBuild.2.3.1— aggregation, MSBuild clean, and Markdown list correctness fixes.2.4.0— deterministic document metadata, caller metadata, and authoritative output layouts.
See TODO.md, docs/roadmap.md, and the ADR index for project history and future work.
Report bugs and feature requests in the GitHub issue tracker.