Resource IDs and Xrefs

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 Antora 3.2.1 and checked on 2026-10-26. Every file that Antora classifies into a component version receives a resource ID, and every cross-reference, include directive and image macro in a page is resolved through that ID rather than through a file path or a URL. The examples use the Pocket Units library (C# and Python) documented as the component pocket-units with versions 1.0 and 2.0, a ROOT module and a guides module, plus a versionless component named shared.

What a resource ID is and why it exists

A resource is a source file of the pages, images, partials, examples or attachments family. Pages, images and attachments are publishable: Antora publishes them even when nothing references them. Partials and examples are published only when a publishable resource includes them, directly or through other partials and examples. Antora gives each resource a unique identifier built from the properties of the source file, and uses that identifier to look the resource up in its virtual file system.

The ID is deliberately independent of two things:

  • The published URL. A reference such as xref:getting-started.adoc[] keeps working if the site moves, if the urls.html_extension_style key changes or if the page is published under a different version segment.

  • The physical location in Git. The ID describes the position of the file in Antora’s classification (component version, module, family, path inside the family directory), not the path from the referencing file to the target. A reference never contains ../ to climb out of a directory, even when the referencing page sits in a subdirectory.

Two resources cannot share an ID. If two content roots declare the same component name and version in antora.yml and both contain a file at the same path below the same family directory, Antora stops the build with a fatal error such as Duplicate page: ..., or Duplicate nav file: ... when both roots register navigation files. The remedy is to remove the redundant content root from the playbook, change the version key in one of the descriptors, or remove the conflicting file from one of the roots (the last case applies to a component version that is intentionally distributed over several roots).

Anatomy of a resource ID

A full resource ID has five coordinates in a fixed order:

version@component:module:family$path/to/file.ext
flowchart LR V["version@"] --> C["component:"] --> M["module:"] --> F["family$"] --> P["path/to/file.ext"] classDef opt stroke-dasharray: 4 3 class V,C,M,F opt

The four coordinates drawn with dashed outlines are optional in most references, and the file coordinate is always present. Each coordinate is terminated by its own delimiter, which is why the delimiters stay in the ID even when the coordinate before them is left out.

Coordinate Delimiter and example Meaning and default

version

2.0@, ends with an at sign and always comes first

Version of the component. Needed to point at another version of the same component or at a specific version of another component. If a component is given without a version, Antora picks the latest version of that component when it resolves the reference. If both version and component are omitted, the version of the current page is used.

component

pocket-units:, ends with a colon

Name of the component, the name key of antora.yml. Needed only when the target is in a different component. Omitted, it means the component of the current page.

module

guides:, ends with a colon

Name of the module directory below modules/. Needed only when the target is in a different module. The module of the root directory is called ROOT.

family

page$, partial$, example$, image$, attachment$

The family directory name with the trailing s replaced by a dollar sign. Whether it is required depends on the target family and on the macro that holds the reference, see the table below.

file

reference/units.adoc

Path of the file relative to its family directory, with its extension. The extension may be dropped only for a partial or an example that has none, such as a Dockerfile. Always required.

When the family coordinate is required

The family is implied by the macro used in most cases, so it is usually left out:

Resource Written with Family coordinate Example

Page

xref macro

Not required, page$ is applied

xref:getting-started.adoc[]

Page

include directive

Not required

include::reference/units.adoc[]

Partial

include directive

Required, partial$

include::partial$footer.adoc[]

Example

include directive

Required, example$

include::example$convert.cs[]

Image

block or inline image macro

Not required

image::logo.svg[]

Image

xref macro

Required, image$

xref:image$logo.svg[Logo file]

Attachment

xref macro

Required, attachment$

xref:attachment$units.csv[Download]

Where the file coordinate starts

The file coordinate is relative to the family directory, never to the page that holds the reference. A page at modules/ROOT/pages/reference/rounding.adoc that links to its neighbour modules/ROOT/pages/reference/units.adoc therefore writes reference/units.adoc, not units.adoc. When the target and the current page are in the same subdirectory of the family directory, the token ./ stands for that shared directory, so ./units.adoc is a valid shorthand from rounding.adoc. The token is only for that case; Antora has no ../ token.

Reserved characters in a file name are written URI-encoded, for example c%2B%2B.adoc for c++.adoc, and Antora decodes them before resolving. A module whose name ends with the name of an AsciiDoc macro (link, kbd, menu) needs a backslash before the macro part, for example mono\link, so that Asciidoctor does not read it as a macro.

Relative and absolute IDs

A reference that leaves out coordinates is relative: Antora fills each missing coordinate from the page that contains the reference. A reference that spells out every coordinate is absolute and means the same thing wherever it appears. Both forms can be mixed freely; the more coordinates are spelled out, the less the reference depends on where the page lives.

Taking modules/ROOT/pages/links.adoc of pocket-units 2.0 as the current page, these all name the same target page, modules/ROOT/pages/getting-started.adoc:

* xref:getting-started.adoc[]
* xref:page$getting-started.adoc[]
* xref:ROOT:getting-started.adoc[]
* xref:pocket-units:ROOT:getting-started.adoc[]
* xref:2.0@pocket-units:ROOT:getting-started.adoc[]
* xref:2.0@pocket-units:ROOT:page$getting-started.adoc[]

Running Antora 3.2.1 on a scratch copy of Pocket Units with those links, plus the ones in the next sections, the rendered HTML of each form was an ordinary link of class xref page to the same published page:

<a href="getting-started.html" class="xref page">Getting Started</a>

The link text is the title of the target page when the brackets are empty and no fragment is present, as the output above shows. Text between the brackets replaces it.

Xrefs to pages

The sections below follow the order in which the coordinates are added. All the results were produced with the playbook and content of the scratch Pocket Units site; the rendered link is shown next to each reference.

Same module

Only the file coordinate is needed. A page in a subdirectory of pages/ is addressed from the pages/ directory:

* Same module: xref:getting-started.adoc[]
* Subdirectory: xref:reference/units.adoc[]
* Same subdirectory: xref:./units.adoc[]

Fragments

A fragment is the ID of a section, block or inline anchor in the target page. It starts with # and goes directly after the file coordinate, before the brackets. A fragment is not checked: the resolver confirms that the page exists, not that the anchor exists inside it.

* Fragment: xref:reference/units.adoc#length[Length]
* Fragment in the current page: xref:#sec[this section]
* Dead fragment, still resolves: xref:getting-started.adoc#nofrag[E]

In the build all three produced links without any log message: reference/units.html#length, #sec and getting-started.html#nofrag. When a fragment is present and the brackets are empty, the link text is the published URL of the target page, not its title, so give fragments explicit text. The same-page form xref:#sec[] is not described in the Antora page documentation; it produced the plain #sec link in the scratch build.

Another module

Add the module coordinate and the colon. The module ROOT is a module like any other and is named when the reference comes from a different module:

* From ROOT to guides: xref:guides:converting-lengths.adoc[]
* From guides back to ROOT: xref:ROOT:getting-started.adoc[Getting Started]

Another component

Add the component coordinate. When the target is in the ROOT module of that component, the module name may be left out, but its colon must stay, which produces the double colon:

* ROOT module of another component: xref:shared::style-guide.adoc[]
* Named module of another component: xref:pocket-units:guides:custom-units.adoc[]

Without a version, Antora links to the latest version of that component at the time of the build. The example in the official documentation shows the link changing from 1.0 to 1.5 when a 1.5 version is added, with no edit to the page. The latest version is the one chosen by Antora’s version sorting; see Versioning and Prerelease.

Another version

Add the version and the at sign in front. The version is written exactly as the version key of the target antora.yml (a string such as 1.0, not a number):

* Same component, other version: xref:1.0@pocket-units::index.adoc[v1.0 home]
* Other version, named module: xref:1.0@pocket-units:guides:converting-lengths.adoc[]

Pocket Units 2.0 renders the first link as ../1.0/index.html. A page that links to another version of its own component should name the component as well as the version when the target is in a different module or when the page may later be copied into another component.

Versionless components

A component whose descriptor sets version: ~ is versionless. A reference that omits the version resolves to it, and in the scratch build xref:shared::style-guide.adoc[] produced ../../shared/style-guide.html. Writing the version explicitly as _@ also resolved:

* xref:shared::style-guide.adoc[]
* xref:_@shared::style-guide.adoc[]

A reference with a real version number to a versionless component does not resolve. xref:1.0@shared::style-guide.adoc[] was reported as unresolved in the same build.

Xrefs to attachments and images

An xref to a non-page resource must name the family, because the xref macro assumes page$ otherwise. The target is then a link, not an embedded image:

* Attachment: xref:attachment$units.csv[Download CSV]
* Image file as a link: xref:image$logo.svg[Logo file]

The rendered links carried the class of the family and the output folder of the target:

<a href="_attachments/units.csv" class="xref attachment">Download CSV</a>
<a href="_images/logo.svg" class="xref image">Logo file</a>

The directories _attachments and _images are in the published output. Jekyll drops folders that start with an underscore, which matters when GitHub Pages deploys from a branch without a .nojekyll file; see Publishing.

An xref to an example (xref:example$convert.cs[]) or a partial does not produce a link, because those families are only published as part of the page that includes them. In the scratch build xref:2.0@pocket-units::example$convert.cs[] was reported as an unresolved xref.

Include directives and image macros

The include directive and the image macros take the same resource ID syntax. Only the macro assumptions differ, as the table above shows: partials and examples need their family, images do not.

Includes

include::partial$footer.adoc[]

include::example$convert.cs[tags=usage]

include::guides:partial$note.adoc[]

include::2.0@pocket-units:ROOT:example$convert.cs[tags=usage]

All four resolved in the scratch build, including the include of a partial from another module and the fully qualified example. Pocket Units shows the C# fragment between the tag::usage[] and end::usage[] comments of convert.cs, so the listing in the page is the single line that converts 10 kilometres to miles. Tags, line ranges and the rules for examples are described in Partials, Examples and Attachments.

Images

Block and inline image macros accept a file coordinate relative to the images directory, with the module, component and version coordinates added when the image lives elsewhere:

image::logo.svg[Logo,width=40]

image::guides:diagram.svg[Diagram,width=20]

image:logo.svg[Logo,width=16] inline

The image from the guides module was rendered as guides/_images/diagram.svg, while the image from ROOT was rendered as _images/logo.svg. A block image macro must stay on one line, otherwise Asciidoctor treats it as ordinary text and the build reports nothing.

Unresolved references and how they are reported

Antora resolves references while it converts each page. A reference that does not point to a classified resource is not fatal by default. The log reports an error and the output contains a visible marker. In the scratch site a page containing the following lines:

* xref:missing.adoc[A]
* xref:guides:nope.adoc[B]
* xref:nocomp::index.adoc[C]
* xref:9.9@pocket-units::index.adoc[D]

image::absent.svg[Absent]

include::partial$absent.adoc[]

printed one log record per problem. Pretty-printed with --log-format=pretty, with the path of the scratch directory shortened, the output was:

[10:55:51.099] ERROR (asciidoctor): target of include not found: partial$absent.adoc
    file: .../src/pocket-units/modules/ROOT/pages/broken.adoc:11
    source: .../src/pocket-units (branch: v2.0 <worktree>)
[10:55:51.100] ERROR (asciidoctor): target of xref not found: missing.adoc
    file: .../src/pocket-units/modules/ROOT/pages/broken.adoc
    source: .../src/pocket-units (branch: v2.0 <worktree>)
[10:55:51.100] ERROR (asciidoctor): target of xref not found: guides:nope.adoc
[10:55:51.100] ERROR (asciidoctor): target of xref not found: nocomp::index.adoc
[10:55:51.101] ERROR (asciidoctor): target of xref not found: 9.9@pocket-units::index.adoc
[10:55:51.102] ERROR (asciidoctor): target of image not found: absent.svg

(the file and source lines of the last four records have the same shape as the first two). The message repeats the target exactly as it was written, so a typo in the module, the component or the version shows up verbatim in it. The location line gives the file and, for includes, the line number.

The HTML that Antora wrote for the same lines shows the markers a reader sees:

<a href="#missing.adoc" class="xref unresolved">A</a>
<div class="imageblock unresolved">
<div class="content"><img src="absent.svg" alt="Absent"></div>
</div>
<div class="paragraph">
<p>Unresolved include directive in modules/ROOT/pages/broken.adoc - include::partial$absent.adoc[]</p>
</div>

The unresolved xref becomes a link whose target is a fragment of the current page (# plus the failed target) and carries the classes xref unresolved; the image gets the class unresolved on its block, and the include is replaced by a paragraph that names the include and the page that holds it. None of these stops the build.

Making a broken reference fail the build

The playbook key runtime.log.failure_level (CLI option --log-failure-level) sets the lowest log level that makes the run fail. With the default fatal the broken scratch site above built completely and exited with status 0. With the level lowered to error, the same site is still built, and the command exits with status 2 (Antora 3.2 changed the exit code of this case to 2, according to its release notes):

npx antora --log-failure-level=error antora-playbook.yml
echo "exit status: $?"
exit status: 2

A CI job that publishes documentation usually sets --log-failure-level=warn or error, so that an unresolved xref blocks the deployment. The workflow files of this site build without that option; see CLI, Docker, Cache and Private Repositories for the options and the exit codes.

Typical causes

Message or symptom Likely cause

target of xref not found: units.adoc#length from a page in pages/reference/

The file coordinate was written relative to the page. From a subdirectory it must be reference/units.adoc or ./units.adoc.

target of xref not found: guides:nope.adoc

Wrong file name, or the module directory has a different name than the module coordinate.

target of xref not found: nocomp::index.adoc

The component name differs from the name key of the target antora.yml, or the content source that provides it is not in the playbook or the branch was not selected.

target of xref not found: 9.9@pocket-units::index.adoc

No component version with that exact version string was built. A branch glob in the playbook may not match.

target of xref not found: 1.0@shared::style-guide.adoc

The component is versionless; leave the version out or write _@.

target of xref not found: 2.0@pocket-units::example$convert.cs

An xref can link only to pages, attachments and images.

target of image not found: absent.svg

The file is not in the images directory of the module, or the module coordinate is missing.

target of include not found: partial$absent.adoc

The file is not in the partials directory of the module, or it is in another module.

A link silently stops working in the browser

The target exists but the fragment does not. Antora does not validate fragments.

Pitfalls and version notes

  • Do not use shorthand xrefs of the form <<other-page.adoc#>> for resources outside the current document, because Antora cannot track them as resources. The Antora documentation advises the macro form.

  • When a reference works in a local build and fails in the published one, check that the playbook used by the deployment pulls the same branches. A component that is available only on some branches is invisible to the others. Source: Antora docs: Xref