AsciiDoc Attributes and Extensions

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.

Antora converts every page with Asciidoctor.js, and two settings tune that conversion: the attributes that are defined for a page and the Asciidoctor extensions that are loaded. This page was written against Antora 3.2.1 and checked on 2026-10-26. The examples were built with Antora 3.2.1 in a scratch site based on the Pocket Units running example; the Irurueta Docs playbook itself pins Antora 3.1.15 in its package.json, and everything described here also applies to the 3.1 line unless a version is named.

Why attributes and extensions matter

An AsciiDoc attribute is a named value. Some attributes are built into AsciiDoc and switch behaviour on and off (experimental, stem, sectanchors), some are defined by Antora (page-component-name, imagesdir), and the rest are custom values an author invents, such as a product name that appears on fifty pages. Defining a custom value once and referencing it everywhere is the main reason to use them.

A documentation site has three natural places to define an attribute, and Antora supports all three:

  • the playbook, which defines site attributes for every page of every component in one run;

  • the component version descriptor antora.yml, which defines attributes for the pages of one component version;

  • the page header, which defines attributes for that page only.

An Asciidoctor extension is JavaScript that adds a new block, a new macro or a new converter behaviour to AsciiDoc itself, for example rendering a LaTeX formula or turning a diagram description into an image. It is a different thing from an Antora extension, which hooks into the site generator (see Antora Extensions).

The three attribute scopes

Site attributes in the playbook

Site attributes live under asciidoc.attributes in the playbook (see the playbook page). Each key is an attribute name and each value a YAML scalar.

asciidoc:
  attributes:
    experimental: ''
    mathjax-tex-tags: ams
    page-team: Docs Team@

The playbook of this site defines three attributes: experimental (which enables the keyboard, button and menu macros) and the MathJax settings mathjax-tex-tags and mathjax-tex-packages, described below.

Component attributes in antora.yml

The same asciidoc.attributes key can be added to the component version descriptor. It applies to every page of that component version, so a documentation branch for version 1.0 can carry different values from the branch for 2.0.

antora.yml
name: pocket-units
title: Pocket Units
version: '2.0'
nav:
- modules/ROOT/nav.adoc
asciidoc:
  attributes:
    product-name: Pocket Units
    page-owner: docs-team

The official page notes that a value starting with a brace has to be quoted in YAML, and that a backslash in front of an attribute reference stops Asciidoctor from substituting it. Attribute values are strings: the official pages advise against the YAML boolean true, since it is most likely coerced to the string true. According to the Antora 3.2 release notes, 3.2 changed how values are parsed (a YAML tag is needed for a value to be parsed as YAML, and quotes are no longer stripped), so check the values of an upgraded site that relied on the old behaviour.

Page attributes in the header

Anything written in the page header, between the title and the first blank line, is a page-level definition.

= Converting Lengths
:product-name: Pocket Units for Teams
:page-reviewed: 2026-10-26

Welcome to {product-name}.

Hard and soft values, and precedence

Playbook values are the strongest, component values are next and the page header is the weakest. A layer can only change a value defined above it when that value is soft. The notation is part of the YAML value:

Write in YAML Meaning Effect on layers below

value or ''

Hard set

The value is locked. A component or page that defines the attribute again is ignored.

value@

Soft set

The value is a default. A component or page can replace it or unset it.

'@'

Soft set with an empty value

A bare @ is not valid YAML, so it is quoted.

~

Hard unset (YAML null)

The attribute stays undefined and cannot be set by a lower layer.

false

Soft unset

The attribute starts undefined but a lower layer can set it.

Playbook, antora.yml and page header layers with the hard and soft rules

The figure summarises the order: the playbook is applied first, then the component descriptor, then the page header, and each arrow only passes a change through when the value above is soft.

A scratch experiment

To see the rules rather than just read them, a scratch site was built with a playbook, a component descriptor and a page that all define the same names. The playbook contained:

asciidoc:
  attributes:
    site-hard: from-playbook
    site-soft: from-playbook@
    comp-soft: from-playbook@
    experimental: ''
    hard-unset: ~
    soft-unset: false

The component descriptor set site-hard: from-component, site-soft: from-component, comp-soft: from-component@ and comp-hard: from-component. The page header set every one of those names to from-page, plus hard-unset and soft-unset. The values rendered in the page body were:

Attribute Rendered value Why

site-hard

from-playbook

Hard set in the playbook, so neither the component nor the page changed it.

site-soft

from-component

Soft in the playbook, so the component replaced it. The component value is hard, so the page could not.

comp-soft

from-page

Soft in the playbook and soft in the component, so the page won.

comp-hard

from-component

Hard in the component, so the page was ignored.

hard-unset

undefined

Hard unset in the playbook. The reference stayed as literal text and Asciidoctor reported a missing attribute warning.

soft-unset

from-page

Soft unset in the playbook, so the page was allowed to set it.

An undefined reference produces the warning skipping reference to missing attribute and the text of the reference is left in the page. When a reference is optional, guard it with an ifdef conditional on the attribute name.

Choosing where to define a value

  • Put values that must be identical across the whole site in the playbook as hard values. This site does so for the MathJax settings.

  • Put defaults that individual components or pages may legitimately change in the playbook or antora.yml with a trailing @. A good example from the official page is page-toclevels, which is set softly so a page can override the table of contents depth.

  • Put content values such as product names in antora.yml, so each documentation branch carries the value that matches its version.

  • Keep values that belong to one page in that page’s header.

Built-in attributes that Antora sets

Antora sets several AsciiDoc attributes for you, and the playbook page says that some of them, such as imagesdir, cannot be overridden (in the scratch site imagesdir was _images). Other built-in attributes do not apply in Antora, for example data-uri, docinfo and outfilesuffix.

The attributes below are read-only and describe the page being converted. Their values come from the scratch site built from a local Git repository on the branch main:

Attribute Value in the scratch site Meaning

docname

index

The page file name without extension.

page-component-name

pocket-units

The component name from antora.yml.

page-component-version

2.0

The component version.

page-component-title

Pocket Units

The component title.

page-module

ROOT

The module the page belongs to.

page-relative-src-path

index.adoc

The path of the page relative to the pages directory of its module.

page-origin-type

git

Whether the content came from a Git repository or a plain directory.

page-origin-url

the repository URL

Where the content was read from.

page-origin-refname

main

The branch or tag name.

page-origin-reftype

branch

Whether the reference is a branch or a tag.

A reference to page-origin-startpath stayed unresolved in this test because the scratch repository had no start_path. The official intrinsic attributes page was not available when this page was checked, so treat the table as a sample of what was observed rather than the complete list.

The page- prefix has one extra meaning. The official page attributes page explains that an attribute whose name starts with page- is promoted into the UI model, so a template can read it as page.attributes.<name> with the prefix removed. An attribute without the prefix is not exposed this way. It also lists three reserved names: page-aliases (alternate resource IDs that keep old links working), page-layout (which UI template renders the page) and page-partial. page-pagination and page-toclevels are used by the default UI. The playbook, the component descriptor or the page header can all set a page- attribute.

Asciidoctor extensions with asciidoc.extensions

Extensions are registered under asciidoc.extensions in the playbook. The key takes a list of npm module names or file paths, and it must not be confused with antora.extensions, which registers site generator extensions. The official page describes the forms of an entry:

  • a module name such as '@djencks/asciidoctor-mathjax', resolved from the node_modules of the playbook project or the global node_modules;

  • a relative or absolute file path such as ./lib/unit-macro, resolved relative to the playbook;

  • a module whose name ends in a file extension needs a trailing slash (for example highlight.js/), otherwise Antora treats it as a local file and the error is typically Cannot find module;

  • the CLI option -r or --require preloads a script or module, in which case the script must register itself because Antora only requires it.

An extension is either global or scoped. A global extension exports a function that Antora hands to Asciidoctor’s static registry, so it is shared by all documents including navigation files. A scoped extension exports register(registry, context) and registers itself on a registry created for each page, which receives the content catalog, the file and the AsciiDoc configuration. The official page states that scoped extensions apply to pages and not to navigation files, and it describes asciidoctor-kroki as a scoped example.

flowchart LR P["antora-playbook.yml"] --> A["asciidoc.extensions: Asciidoctor extensions"] P --> B["antora.extensions: site generator extensions"] A --> C["MathJax, Kroki, custom macros: run while a page is converted"] B --> D["Mermaid, Lunr, Collector: run at generator events"]

A custom scoped extension

The following extension adds an inline macro and was run in the scratch site, where it was listed after the other extensions.

lib/unit-macro.js
'use strict'

module.exports.register = function (registry, context) {
  registry.inlineMacro('unit', function () {
    const self = this
    self.process((parent, target) => {
      const text = `<span class="unit">${target}</span>`
      return self.createInline(parent, 'quoted', text, { type: 'unquoted' })
    })
  })
}
antora-playbook.yml (excerpt)
asciidoc:
  extensions:
  - ./lib/unit-macro

A page containing the text unit:km[] came out as <span class="unit">km</span>, so the extension ran.

MathJax, as used by this site

This site renders mathematics on the server, with @djencks/asciidoctor-mathjax 0.0.9. The extension replaces Asciidoctor’s own stem processing and writes the equation as inline SVG in the page, so the UI needs no MathJax script. The asciidoc part of the playbook is:

asciidoc:
  attributes:
    mathjax-tex-tags: ams
    mathjax-tex-packages: ams boldsymbol
    experimental: ''
  extensions:
    - '@djencks/asciidoctor-mathjax'

The extension is configured through attributes, which is why the two features belong on one page. According to its README:

  • stem selects the default processor, latexmath or asciimath;

  • mathjax-tex-packages lists the TeX packages to load, for example ams physics;

  • mathjax-tex-tags sets equation numbering and accepts none (the default), ams (needs the ams package) or all. Equations are numbered per page in Antora.

In the scratch site, a [stem] block and an inline latexmath macro were both rendered into <svg> elements inside the HTML, with no MathJax script on the page. Bare TeX delimiters (\( ... \) and \[ ... \]) written directly in the text were not converted and stayed literal, so write formulas with or a [stem] block. Line breaks with a double backslash are not supported by MathJax 3, and the README suggests \displaylines or an ams environment such as aligned instead.

Kroki

asciidoctor-kroki turns diagram source such as PlantUML or GraphViz into an image through a Kroki server. It is not used by this site, which relies on Mermaid. It was installed at version 0.18.1 in the scratch site and listed in asciidoc.extensions:

asciidoc:
  extensions:
  - asciidoctor-kroki

A [plantuml] literal block was converted into an <img> element whose address pointed at the public Kroki server and contained the encoded diagram. In that default configuration the diagram text is therefore sent to the server whenever a reader loads the page. For private diagrams, consult the extension’s README about which server it contacts and whether the image can be produced at build time; those options were not tried here.

Mermaid is an Antora extension

Mermaid diagrams on this site do not use asciidoc.extensions. @sntke/antora-mermaid-extension is listed under antora.extensions: it finds [mermaid] blocks, leaves their text in the page and adds a script to the UI’s header-scripts partial that loads Mermaid in the reader’s browser. In the scratch site the generated script was import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; mermaid.initialize({"startOnLoad":true}); and the block became a <div class="mermaid content"> containing the diagram source.

antora:
  extensions:
    - require: '@sntke/antora-mermaid-extension'
      mermaid_library_url: https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs
      script_stem: header-scripts
      mermaid_initialize_options:
        start_on_load: true

The extension depends on the UI having a header-scripts partial. Its README lists further options such as elk_library_url and mermaid_run_options; option keys are written in snake case in the playbook.

Pitfalls

  • Defining antora.yml attributes without @ locks them against pages. The scratch run showed site-soft stuck at the component value because the component did not append @.

  • Hard unset (~) leaves references as literal text and logs a warning, which can break a build run with a strict --log-failure-level.

  • An Asciidoctor extension belongs under asciidoc.extensions and a site generator extension under antora.extensions; the Mermaid extension used here is the second kind.

  • A module name ending in a file extension needs a trailing slash.

  • The page- prefix is what makes an attribute reachable from UI templates. Without it the value is only available in AsciiDoc content.

  • Server-side MathJax makes pages larger because each equation is an SVG, but it avoids loading a script in the browser. Kroki’s default setup sends diagram text to a third party.