Configure an Extension

An extension that you write or use may require additional settings to configure its behavior. While the extension could tap into information already available in the playbook or the content sources Antora is building, it may be necessary to configure the extension directly. An extension can accept an arbitrary number of properties, which may be nested, that are specified in the entry in the playbook file. These properties are accessible via a context variable named config that’s passed to the extension’s register function.

Basic configuration

Let’s assume that we want to publish a file named humans.txt to give credit to the people who make the documentation site possible. We’ll name our extension humans-txt-extension.js. Of course, the extension won’t know who to credit, so we’ll need to pass it some configuration.

Let’s start by registering our new extension in the playbook and passing in the list of people using the names key. The value of this key will be an array of names.

antora:
  extensions:
  - require: ./humans-txt-extension.js
    names:
    - Doc Writer
    - Dr. Austen
    - Emily Story

To make room for the additional keys, we’ve converted the entry for the extension from a single (string) value to a map. The require request value slides into the require key. That leaves room to define additional keys, in this case names.

Now let’s write an extension that accepts this configuration and uses it to create the humans.txt file:

Example 1. humans-txt-extension.js
module.exports.register = function ({ config }) {
  this.on('beforePublish', ({ siteCatalog }) => {
    const teamInfo = '/* TEAM */\n' + config.names.map((name) => `Name: ${name}`).join('\n')
    const contents = Buffer.from(teamInfo + '\n')
    siteCatalog.addFile({ contents, out: { path: 'humans.txt' } })
  })
}

The config object for the extension is accessed using object destructuring just like any other context variable. Thanks to variable scoping in JavaScript, we can still access that variable in our listener for the beforePublish event. We use the information it provides to populate the contents of the humans.txt file and add it to the site catalog. Antora will then include humans.txt file in the published site.

Configuration key transformation

In YAML, key names use the snake_case naming convention. In JavaScript, property names use the camelCase naming convention. To help bridge the naming convention mismatch between YAML and JavaScript, Antora automatically transforms snake_case key names in the playbook file into camelCase properties on the configuration object. For example, Antora transforms cache_dir to cacheDir. Most of the time, this isn’t a problem. However, if your extension passes configuration or data on to another application, this transformation can be problematic.

Configuration data

To bypass this configuration, you can tuck keys away inside the data key. Any keys inside the data key (at any depth) are passed through without being modified.

Let’s assume that we want to specify structured content for our humans.txt file. We don’t need Antora to transform this structured content, so we can store it inside a key named data.

antora:
  extensions:
  - require: ./humans-txt-extension.js
    data:
      TEAM:
      - Lead Writer: Doc Writer
        Contact: doc [at] example.org
        Location: Denver, CO
      - Information Architect: Dr. Austen
        Location: Winchester, Hampshire, England
      - Narrator: Emily Story
        Location: Antwerp, Belgium

Now, the extension can iterate over the keys in config.data and layout the contents of the humans.txt file.

const contents = Buffer.from(
  Object.entries(config.data).reduce((accum, [category, entries]) => {
    if (accum.length) accum.push('')
    accum.push(`/* ${category} */`)
    entries.forEach((entry) => {
      accum.push('')
      for (const [key, val] of Object.entries(entry)) accum.push(`${key}: ${val}`)
    })
    return accum
  }, []).join('\n')
)