Links and Cross References
|
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 in the mcr.microsoft.com/dotnet/sdk:10.0 Docker image, using the default
and modern templates, over the Pocket Units sample (a C# library with an articles folder). It covers links between
files, links by identifier (UID), links to the .NET class library and links between separate DocFX sites. The
headers that declare a uid are described in Markdown, and the xref
key of the configuration file in docfx.json.
Two kinds of link
DocFX offers two mechanisms, and choosing the right one for each case avoids most broken links:
-
Links by path: ordinary Markdown links (
[text](path)) to a file. DocFX checks that the target file is part of the build and rewrites the source extension (.md) to the output one (.html). -
Links by identifier: cross references (
xref) to anything that has auid. A UID is independent of the file name and of the folder, so the link survives when a file moves, and it can point to an API member, to a conceptual article or to a page of another site.
The resolution order is shown below: a path link is checked against the files of the build; a cross reference is
looked up first in the UIDs of the current project (conceptual headers and generated API pages) and then in the
cross-reference maps listed in build.xref.
path to a file"] -->|"file in build?"| F{"found"} F -->|"yes"| H1["href to the .html output"] F -->|"no"| W1["warning InvalidFileLink
link left as written"] X["xref with a UID"] --> L{"UID in project?"} L -->|"yes"| H2["href to the page or member"] L -->|"no"| M{"UID in a map from build.xref?"} M -->|"yes"| H3["href from the map"] M -->|"no"| W2["warning UidNotFound"]
Figure: how DocFX resolves a path link and a cross reference.
Links by path
The syntax is plain Markdown. A link to an absolute URL is left alone. A link that starts with / is domain
independent and points to the root of the server that hosts the site. A relative path is relative to the file that
contains the link. The prefix ~/ makes the path relative to the folder that holds docfx.json instead, which
is easier to maintain in deep folder trees. ~/ is valid in images and in the HTML elements a, img, script and
link as well; DocFX rewrites the href or src of those elements in the same way.
The Pocket Units project used for this page has this layout (the metadata output api/ is generated):
docfx.json
toc.yml
index.md
images/logo.svg
articles/
toc.yml
tour.md (uid: PocketUnits.Tour)
links.md (uid: PocketUnits.Links)
src/PocketUnits/ (the C# library)
{
"metadata": [
{ "src": [{ "src": "src", "files": ["**/*.csproj"] }], "dest": "api" }
],
"build": {
"content": [
{ "files": ["api/**.yml", "toc.yml", "index.md", "articles/**/*.md", "articles/**/toc.yml"] }
],
"resource": [{ "files": ["images/**"] }],
"xref": ["https://learn.microsoft.com/en-us/dotnet/.xrefmap.json"],
"output": "_site",
"template": ["default", "modern"]
}
}
The articles/links.md file below contains path links, among them one to a file that does not exist:
Markdown link: [the tour](tour.md). Root-relative: [the tour again](~/articles/tour.md).
Missing file: [gone](missing.md).
Image: 
HTML: <a href="tour.md">html tour</a>
The build log reports the missing file, and the rendered HTML shows that the valid links point at tour.html and at the
copied image, while the broken one keeps the .md source path:
/w/articles/links.md(7,1): warning InvalidFileLink: Invalid file link:(~/articles/missing.md).
<p>Markdown link: <a href="tour.html">the tour</a>. Root-relative: <a href="tour.html">the tour again</a>.
Missing file: <a href="missing.md">gone</a>.</p>
<p>Image: <img src="../images/logo.svg" alt="logo"></p>
<p>HTML: <a href="tour.html">html tour</a></p>
Three details come from the official page and from the build above:
-
The target file must be included in
build.content(orbuild.resourcefor images and downloads). A file that exists on disk but is not part of the build produces the sameInvalidFileLinkwarning. -
A path link names the source file, even when a file mapping moves the file to another output folder. The
~/prefix is relative to the folder ofdocfx.json, not to the site root. With a resource mapping that copies../App.Specs/screenshots/About.pngtoassets/images, the link is written with the source path (~/../App.Specs/screenshots/About.png) and DocFX rewrites it to the output location. Writing the output path (~/assets/images/About.png) is reported asInvalidFileLink. -
The Markdown autolink form does not support relative paths:
<file.md>is treated as an HTML tag, not as a link.
Cross references by UID
A UID is declared in the YAML header of a conceptual file, and is generated automatically for every namespace, type and
member that docfx metadata documents:
---
uid: PocketUnits.Tour
---
# A tour of Pocket Units
Convert a length with `Converter.Convert`.
For API pages the UID is the mangled signature: the namespace PocketUnits, the type PocketUnits.Converter, the
enumeration PocketUnits.LengthUnit, and the method
PocketUnits.Converter.Convert(System.Double,PocketUnits.LengthUnit,PocketUnits.LengthUnit). The easiest way to find a
UID is to open the generated xrefmap.yml (see below) or the YAML file of the page under api/. A trailing asterisk, as in
PocketUnits.Converter.Convert*, refers to all overloads of a method.
There are three equivalent ways to write a cross reference.
Autolink
<xref:uid> is a Markdown autolink with the scheme xref. The text of the link is the title of the target (the name
of an API member, the first heading of an article).
Link
[text](xref:uid) is the normal link syntax with the xref scheme, with your own link text. An empty text, as in
[](xref:uid), takes the generated text.
The @ shorthand
@uid is a shorthand that is only accepted in prose. Because @ is common in text (e-mail addresses, decorators), the
parser only treats a string after @ as a cross reference when it starts with a letter and ends at whitespace, at the
end of the line, or at punctuation (one of the characters period, comma, semicolon, colon, exclamation mark, question mark, backtick or tilde) followed by whitespace, or when it is
enclosed in single or double quotes (@"uid?options"). An unknown UID in the shorthand form still produces a warning.
In the build above, me@example.com was left as text.
The three forms in articles/links.md:
Autolink: <xref:PocketUnits.Tour>
Link form: [read the tour](xref:PocketUnits.Tour)
Shorthand: see @PocketUnits.Tour for details, and @PocketUnits.Converter.
Email-like text: write to me@example.com, not a link.
Rendered by the build:
<p>Autolink: <a class="xref" href="tour.html">A tour of Pocket Units</a></p>
<p>Link form: <a class="xref" href="tour.html">read the tour</a></p>
<p>Shorthand: see <a class="xref" href="tour.html">A tour of Pocket Units</a> for details, and <a class="xref" href="../api/PocketUnits.Converter.html">Converter</a>.</p>
<p>Email-like text: write to me@example.com, not a link.</p>
All three forms produce an anchor with the CSS class xref. The same references also work inside XML documentation
comments: <see cref="LengthUnit"/> is converted by docfx metadata into an xref element with the UID of the type, and
the build resolves it like any other (see .NET API Docs).
HTML written in a Markdown file can also use <xref href="uid"></xref> (and <a href="xref:uid"></a>); the
alt, altProperty and displayProperty options below are documented on those forms.
Options of a cross reference
Options are appended to the UID as a query string. They apply to all three syntaxes; in the shorthand form the whole
reference must be quoted when it contains ?.
| Option | Meaning |
|---|---|
|
The text shown when the reference is resolved, replacing the generated text. |
|
The text shown when the reference cannot be resolved to a link (the target has no |
|
The property of the target used as the text when it is resolved, such as |
|
The property used as the text when there is no |
|
The |
The following references were built against the Pocket Units API and the .NET class library (see the next section for the
System ones):
Class with options: <xref:PocketUnits.LengthUnit?displayProperty=fullName>, [](xref:PocketUnits.Converter?title=Converter+class)
Method with text: @"PocketUnits.Converter.Convert*?text=Convert"
BCL: <xref:System.String>, <xref:System.String?displayProperty=fullName>, <xref:System.String.Length?displayProperty=nameWithType>
Alt: <xref href="Not.Defined.Type?alt=NotDefinedType"/>
Missing: <xref:Missing.Type>
<p>Class with options: <a class="xref" href="../api/PocketUnits.LengthUnit.html">PocketUnits.LengthUnit</a>, <a class="xref" href="../api/PocketUnits.Converter.html" title="Converter class">Converter</a></p>
<p>Method with text: <a class="xref" href="../api/PocketUnits.Converter.html#PocketUnits_Converter_Convert_">Convert</a></p>
<p>BCL: <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">String</a>, <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string">System.String</a>, <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.string.length#system-string-length">String.Length</a></p>
<p>Alt: <span class="xref">NotDefinedType</span></p>
<p>Missing: <xref:Missing.Type></p>
An unresolved reference does not break the build. It is printed as text, and the log gets a UidNotFound warning with
the line number:
/w/articles/links.md: warning UidNotFound: 1 invalid cross reference(s) "<xref:Missing.Type>".
/w/articles/links.md: Details for invalid cross reference(s): "<xref:Missing.Type>" in line 25
The alt option is the one way to silence that warning on purpose: a reference that has alt and no target renders
as the plain span shown above (the Not.Defined.Type reference produced no warning). In the same way, an xref element
in HTML may carry data-throw-if-not-resolved="false", which the metadata stage adds to references taken from
documentation comments.
The .NET class library
References to System types need a map of the types published at learn.microsoft.com. The official page gives the
location of the map that holds the class library types published from the dotnet-api-docs repository, and the
xref key of the configuration file takes it as it is (it is already present in the docfx.json shown above):
{
"build": {
"xref": [
"https://learn.microsoft.com/en-us/dotnet/.xrefmap.json"
]
}
}
With that entry the build log reported 2 external references found in xref maps, the two external UIDs used by the articles
(System.String and System.String.Length). The map is downloaded on every build, so a build
needs network access to the Microsoft site; a build in a restricted network fails to resolve those references.
The official page adds that, for the API reference pages generated from code (the ManagedReference documents), DocFX
resolves the class library types and other types published on learn.microsoft.com without any map, which is why the
double of Converter.Convert is already a link in the generated page while a System.String mention in an article
needs the map.
Linking across DocFX sites with xrefmap.yml
Every build writes an xrefmap.yml into the output folder. It lists each UID of the site with the title of the page
and its URL, so another project can link to yours by UID. The first entries of the Pocket Units file:
### 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
Each entry has a uid, a name (the title or short name), an href (an absolute URL, or a path relative to the
xrefmap.yml file) and, for API members, fullName and nameWithType, which displayProperty can select.
commentId and the language variants such as name.vb come from the metadata stage. The articles PocketUnits.Tour
and PocketUnits.Links appear in the same file with only uid, name and href.
To use the map, a second project lists it in build.xref. The value is a string or an array of strings, each a file
path or a URL (the reference documents the schemes http, https and file):
{
"build": {
"content": [{ "files": ["toc.yml", "index.md"] }],
"xref": ["https://example.org/pocket-units/xrefmap.yml"],
"output": "_site",
"template": ["default", "modern"]
}
}
Source: DocFX docs: docfx.json reference
---
uid: Consumer.Home
---
# Consumer site
Convert with <xref:PocketUnits.Converter>, see @PocketUnits.Tour and [units](xref:PocketUnits.LengthUnit).
The example in the page was built with a map served over HTTP from the Pocket Units output folder (a local server stood
in for example.org), and the references resolved to the other site:
<a class="xref" href="http://host.docker.internal:8765/api/PocketUnits.Converter.html">Converter</a>
<a class="xref" href="http://host.docker.internal:8765/articles/tour.html">A tour of Pocket Units</a>
<a class="xref" href="http://host.docker.internal:8765/api/PocketUnits.LengthUnit.html">units</a>
The relative href values of the map were resolved against the URL of the map. A local file does not behave that
way: with a relative file path such as ../lib/_site/xrefmap.yml in the xref list, the same references were rendered with
href="api/PocketUnits.Converter.html", the relative path copied unchanged into the page, which points to a file that does not exist on the consumer site.
In practice this means: publish the map next to the site and reference it by its HTTP address, or make the href
values absolute (for example with a post-processing step on the file) before using it as a local file. Setting
sitemap.baseUrl did not change the href values written to xrefmap.yml in the test.
A map is only read, never merged into the output of the consumer: its UIDs are not copied to the consumer’s own
xrefmap.yml, and a consumer that republishes its map exports only its own UIDs.
Duplicate UIDs
Each UID should be defined once. When two files define the same UID, DocFX warns and keeps one of them:
/w/articles/dup2.md: warning DuplicateUids: Uid(Dup.Page) has already been defined in articles/dup1.md.
In the build, the reference <xref:Dup.Page> resolved to the first file (dup1.html) and xrefmap.yml listed only
that one. The official Markdown page describes the outcome as undetermined, so do not depend on which file wins;
the order in which the files are processed is not a documented contract.
Conceptual files have no UID unless the header declares one, so a file that is only linked by path needs none. Giving
every article a UID that reflects its subject (PocketUnits.Tour, not articles-tour) makes cross references readable
and lets another site link to it.
The undocumented xrefService
A xrefService setting under build is not described in the
docfx.json reference or in the cross-reference page (the text of the reference file contains no occurrence), and it
was not tested for this page, so its behaviour is not documented here. Use a static xrefmap.yml or the maps published
by other projects, which are the documented mechanisms.
Pitfalls
-
A path link to a file that is not part of
build.contentorbuild.resourceis a warning (InvalidFileLink), not an error, and the link is left as written. Turn warnings into failures with--warningsAsErrorsin continuous integration. -
An unknown UID is also only a warning (
UidNotFound) and the page shows the raw<xref:...>text. -
[text](xref:uid)needs thexref:scheme exactly;[text](uid)is a path link. -
The
@uidshorthand ending with a period needs whitespace after the period, and a UID with?optionsneeds quotes. -
A map referenced as a local file keeps its relative
hrefvalues (see above). -
The
xreflist is downloaded on every build; builds in restricted networks need a copy of the map and a file path.