Asciidoctor Extensions

The asciidoc key can be used to register Asciidoctor extensions.

Install extension code

Before registering an extension in your playbook, you need to install the extension code. You can install it globally, declare it as a dependency of the project, or add the extension script to the playbook project.

extensions key

An Asciidoctor extension is registered using the extensions key. The extensions key accepts a list of node module names (i.e., npm package names) and relative or absolute filesystem paths.

When registering an Asciidoctor extension, make sure you’re using the nested key asciidoc.extensions and not antora.extensions. The latter key is for registering Antora extensions, which use a different extension facility.
Example 1. antora-playbook.yml
asciidoc:
  extensions:
  - name-of-node-module
  - ./path/to/extension
If you’re trying to require a Node.js module (e.g., a package installed from npmjs.com) whose name ends with a file extension, such as .js, you must append a trailing forward slash. For example, the Node.js module highlight.js must be written as highlight.js/. This suffix tells Antora to resolve the request as the name of a module, not as a local file. Without this suffix, Antora assumes the request is a local file. If it’s not a local file, this can result in the error message Cannot find module. If the request is already a local file, the suffix is not needed.

Global Asciidoctor extensions

Global extensions are registered once, before any pages are converted. These extensions are shared by all documents Antora converts using Asciidoctor (including navigation files). A global extension is a node module or script that exports a function when required. Antora passes this function directly to the register method of Asciidoctor’s static extension registry.

To register a global extension, all you need to do is reference its name (if it’s a node module on the require path) or path (if it’s a local script) in the extensions key.

Example 2. Register a global extension provided by a node module
asciidoc:
  extensions:
  - asciidoctor-emoji-macro

In this case, asciidoctor-emoji-macro is the name of an installed node module and is thus available on the require path (either in the node_modules directory in the playbook project or in the global node_modules directory).

Example 3. Register a global extension from a local script
asciidoc:
  extensions:
  - ./lib/shout-block

In this case, the extension is a script located at the path lib/shout-block.js relative to the playbook file.

Here’s an example that shows how to register multiple global extensions:

Example 4. Register multiple global extensions
asciidoc:
  extensions:
  - asciidoctor-emoji-macro
  - ./lib/shout-block

Scoped Asciidoctor extensions

Rather than requiring an extension globally, you may want to register an extension per instance of the AsciiDoc processor. The benefit of this approach is that it allows the extension to hook into the Antora lifecycle. The other difference is that scoped extensions are only registered and used for pages, not for navigation files.

In order to register a scoped extension, the extension must support this mode of usage. Specifically, the extension must export a register function that accepts an extension registry on which it self registers. The function is called with a scoped (per-processor) extension registry and a context object. The context object includes the current file, the content catalog, and the AsciiDoc configuration object from the playbook.

Here’s an example of a register function for a scoped extension:

Example 5. Scoped Asciidoctor extension register function
module.exports.register = function (registry, context) {
  registry.block('shout', createShoutBlock(context))
}

A scoped extension is registered in the playbook in exactly the same way as a global extension.

Example 6. Register a scoped extension from the require path
asciidoc:
  extensions:
  - asciidoctor-kroki
Example 7. Register a scoped extension from a local script
asciidoc:
  extensions:
  - ./lib/equation-macro

The main difference is that if the extension exports the register function, it gets scoped to the processor instance instead of being registered globally.

Preloading extensions

Instead of registering extensions using the playbook, you can preload extensions using the -r or --require CLI option. The value of this option may be either a path to a file (relative to the current directory), or a node module name. The -r option may be specified multiple times.

This option gives site authors the ability to load additional code into the runtime before Antora begins executing, in fact, before Antora is even loaded. The option follows the module resolution rules of the require() function in Node.js. A common use case for this option is to register Asciidoctor extensions globally.

If the node module or script is an Asciidoctor extension, it must self-register with Asciidoctor’s static extension registry when required in order for the extension to be used. (Antora merely requires the script. It does not invoke its exported function).

Here’s an example showing how to use the Antora CLI to preload multiple Asciidoctor extensions:

$ antora -r ./lib/shout-block -r asciidoctor-emoji-macro antora-playbook.yml

The -r option can also be used for other purposes, such as to alter global state or override Antora components.

For more information about the CLI, see Antora CLI Commands.