Andreas' Blog

Adventures of a software engineer/architect

Composing navigation files in Antora using the include macro

2019-05-14 4 min read anoff

This third post in the series of Antora posts quickly addresses a feature I only recently discovered. It will show you some techniques you can use to compose the navigation menu in a multi-repository, multi-module setup. This is especially interesting if you have content spread across repositories that you want to present hierarchically in the navigation tree.

Using multiple navigation files

Let us start with the basics: How to combine navigation files?

The Antora docs explain how you can use multiple navigation files for one component by using the nav key in the component descriptor. However this limits you to the operation of concatenation.

Concatenated navigation content
Figure 1. Concatenating navigation files via component descriptor

To achieve such concatenation just define your component as

name: bp
title: Bird Person
version: HEAD
- modules/ROOT/nav1.adoc
- modules/wedding/nav2.adoc
- modules/friends/nav3.adoc

Composing navigation files by include

In case a plain concatenation does not do it and you want to compose your navigation structure across three different navigation files you can use the include: macro. This can come in handy if you split content into multiple modules and want to include the content of the modules in varying order in the navigation menu.

Inlined navigation content
Figure 2. Inlining navigation content

In this scenario the component descriptor would reference a single navigation file

name: bp
title: Bird Person
version: HEAD
- modules/ROOT/nav1.adoc

But the navigation file in turn references content from additional supplemental navigation files.

.Bird Person
* xref:welcome.adoc[Welcome to Birdperson's life]
* xref:contact.adoc[Contact me]
* xref:wedding:invitation.adoc[Wedding Invitation]
** xref:wedding:attendees.adoc[Attendees]
* Friends of Birdperson
** xref:friends:tammy.adoc[<3 Tammy]
** xref:friends:morty.adoc[Morty]
** xref:friends:rick.adoc[Rick]
Screenshot of the file structure
Figure 3. File structure for included nav files

Given the above navigation files and directory structure you end up with the following navigation tree. Notice how nav2.adoc is embedded in the content defined in nav1.adoc.

Bird Person
  • Welcome to Birdperson’s life
  • Wedding Invitation
    • Attendees
  • Contact me
  • Friends of Birdperson
    • <3 Tammy
    • Morty
    • Rick

For navigation files to be used by include: in Antora you need to place them inside a folder that is indexed by Antora. The suggested solution is storing it as a partial so it does not clutter the actual page space.

Nesting navigation listings across files

There is another interesting scenario of including remote content. Given our simple example this might seem far-fetched but consider the option of defining a high level list structure in nav1.adoc and filling certain branches of this navigation tree with included files. In this scenario we want to include the list of Birdperson’s friends but continue the list indentation from nav1.adoc.

Nested navigation content
Figure 4. Nesting navigation content

By extending Birdpersons relationships also to enemies we want the following nav tree to be rendered:

Bird Person
  • Welcome to Birdperson’s life
  • Wedding Invitation
    • Attendees
  • Relationships
    • Enemies
      • Galactic Federation
    • Friends
      • <3 Tammy
      • Morty
      • Rick
  • Contact me

For the sake of simplicity we will not add any pages for the Enemies / Galactic Federation entries. The changed files for this scenario are nav1 and nav3.

.Bird Person
* xref:welcome.adoc[Welcome to Birdperson's life]
* Relationships
** Enemies
*** Galactic Federation
** Friends
* xref:contact.adoc[Contact me]
** xref:friends:tammy.adoc[<3 Tammy]
** xref:friends:morty.adoc[Morty]
** xref:friends:rick.adoc[Rick]

Notice how nav3.adoc in this case only hosts the sub-elements and not the Friends caption itself. Using the same directory structure as previously the final rendered result in Antora looks like the nested list we expected to get.

Nested navigation tree
Figure 5. Nested navigation tree in Antora

As expected the content of nav2.adoc is included on the same list level as nav1.adoc. The list of friends from nav3.adoc however continues as a nested indentation within ROOT/nav1.adoc. The picture below shows the included sections highlighted according to their respective navigation files.

Nested navigation tree (highlighted)
Figure 6. Nested navigation tree in Antora (highlighted)

Closing words

Dan Allen posted a fantastic answer to my initial question regarding this issue. I encourage you to read it if you have questions about what happens exactly.

Another neat trick is to reuse component definitions if you are forced to spread documentation across different repositories that you actually want to show up in a common navigation tree. This might be the case for multi-repository software projects where you want to create one generic software documentation and aggregate by content (e.g. all interfaces in one place). To achieve this just use the same antora.yml file (same name: and version:) in multiple repositories.

You can view the example directories in and run the playbook-nest.yml.

If you have any questions or know additional tricks let me know via Twitter or leave a comment 👋

Preview AsciiDoc with PlantUML in VS Code

2019-05-08 4 min read anoff
This post is for everyone that likes to write AsciiDoc in VS Code but also wants to inline PlantUML diagrams within their docs. In a previous post about diagrams with PlantUML I gave an intro into PlantUML and how to preview images in VS Code. With the latest release of the asciidoctor plugin for VS Code it is possible to easily preview embedded PlantUML images within AsciiDocs. No more need to maintain attributes in each file 🎉 Continue reading

Advanced customization for Antora - PlantUML support & custom UI

2019-04-19 5 min read anoff

This post will cover some slightly advanced steps for building a multi-repository Asciidoc documentation using Antora. My previous post covered basics of Antora and how to migrate existing AsciiDoc files. In this post I will go through the steps of including content from another repository, building a custom UI and adding plantUML support to the (automated, dockerized) build.

plantUML support

One of the most important things for me when it comes to software docs is the ability to show relationships and interactions using diagrams. As you might know from my previous posts I am a huge plantUML fan. So plantUML support is important both for rendering the Antora website and the local development.

Local preview

For local previews I use the AsciiDoc extension by João Pinto.

VS Code Preview of PlantUML in .aodc
Figure 1. PlantUML preview in VS code

To get the plugin working you need to set the plantuml-server-url Asciidoctor attribute on your page as described on the asciidoctor-plantuml package that is used for rendering in VSC. Sadly this needs to be set for every single adoc file - if you want to preview it.


Antora plantUML rendering

To enable plantUML in Antora you need to register the asciidoctor-plantuml package and configure the same attribute as for local preview. However in the case of Antora you an specify it one in the playbook.yml.

  - asciidoctor-plantuml

Multiple repositories for a single documentation

Another feature I promised to address during my previous post was splitting up content into multiple Antora modules/components. For my arc42 dummy project I used both.

ARC42 table of contents
Figure 2. Table of Contents for ARC42 project

Given the above table of contents the chapters Cross-cutting concepts and Architecture decisions where put into a separate module within the main component. The modules concepts and adr (architecture design record) are moved into separate modules because I prefer to have each concept/adr in a separate .adoc file. For this example a simple subfolder within the ROOT/pages directory might suffice but in a real world scenario having a different module might come in handy to handle these important topics more efficiently. Both concepts and ADRs are aggregated into a single index.adoc file in each module and are included in the main build.

A separate Antora component is used in this example for the Media Manager subsystem that makes up one of the level 1 building blocks. Imagine this subsystem being developed in a separate repository. In this case you want to keep the respective documentation within the MediaManager repository. This is done in the antora-arc42-mediaman repository where all the subsystems source code and documentation shall be kept.

In the example build the Level 2 definition of the Media Manager block diagram view is included from the remote repository of the subsystem. You can see this include on line 178 of the building_block_view.adoc.

So overall the ARC42 build uses four modules spread across two components to build its entire documentation. Some modules provide Antora partials whereas others provide entire pages that get referenced in the main ROOT module.

antora component setup
Figure 3. Multi component, multi module Antora setup

Customizing the Antora UI

For minor modifications of the UI you can use the supplemental_files attribute in the playbook. Supplemental files allow you to exchange parts of the built UI bundle. Given the current state of the default UI that you can find at this is best used to modify the content of header, footer etc.

Applying minor changes via supplement_files

On this commit of the antora-arc42 you can see supplementa_files being defined and used. To modify content from the UI bundle simply check it into the repository of your antora playbook and reference it

Specifying supplemental_files for the Antora UI bundle
    snapshot: true
supplemental_files: ./supplemental-ui

In the repository I applied a custom footer template to the bundle.

<footer class="footer">
  <p>Original arc42 template licensed under <a href="">MIT</a> and modified for antora fit by <a href="">Andreas Offenhaeuser</a>, the page is created using the Antora Default UI licensed under <a href="">MPL-2.0</a></p>

As all style attributes are bundled into a single site.css file it is quite hard to modify the UI style via this method. Dan - the author of Antora - explained it like this in an issue discussion I had in the Antora repository.

Dan Allen on customizing the UI
Figure 4. Dan Allen on customizing the UI

Creating a custom UI bundle

Not being able to modify the generated sites style via the supplemental_files method I set out to create a custom UI bundle for my ARC42 documentation. The main changes I implemented with this bundle are:

  1. custom color theme via src/css/
  2. customized header and footer files
  3. add a custom CSS/JS to provide help text that can be toggled via the toggle help text in the navigation bar
  4. remove the component navigation dropdown as shown in the image below
component navigation dropdown
Figure 5. Antora component navigator

The component navigation has been removed because in the case of the ARC42 documentation the MediaManager component is not a documentation in itself but merely a way of creating a multi-repository architecture documentation. Therefore only a single entrypoint into the documentation is required. If your project has both - Antora components that merely serve as partial/page providers and components that serve as standalone documentation you may want to create a custom navigation option as well.


The Antora ARC42 build now consists of three repositories

  1. the playbook and main ARC42 dos at
  2. an Antora component to provide lower level documentation of the antora-arc42-mediaman to be included in the build
  3. a custom UI bundle

These repositories should act as a good reference to create more advanced builds with Antora while not cluttering the individual repositories with too many features/changes. As with most of my recent projects all automation is done via Drone CI, see the respective .drone.yml repositories in the main repository and the UI bundle for reference.

Screenshot of the page
Figure 6. Screenshot of the final ARC42 Antora build

You can view the final result at with ?help showing all the original ARC42 help texts for each chapter.

If you have any questions or know of better/alternative ways let me know via Twitter, leave a comment or submit changes to this post directly via PR 👋

Continuous Vitae - Auto built and git versioned CV

2019-04-08 6 min read anoff
Versioning your CV is important. One traditional approach is to date it whenever you send it out. I chose to present my CV on my website and host it on GitHub. In this blog post I want to explain how I set up continuous integration pipeline for building my CV that automatically injects a unique version into each build. This method is applicable for anyone choosing to ascii-based CV - in my case LaTeX. Continue reading

Hosting Gitea and Drone with Docker

2019-03-24 8 min read anoff

This post will walk you through setting up a self hosted git based continuous integration environment on a two machine setup - assuming you already have two virtual machines at your disposal. Using Gitea for git hosting and contribution management and Drone for docker-based build jobs, this will guide you through creating docker-compose files as well as configuring the individual services and getting SSL certificates via traefik. Docker and docker-compose knowledge is required for this tutorial. It mostly focuses on the correct configuration of all the services at play here and not explaining their basic functionality.

This tutorial uses Azure resources so some of the aspects might not be 100% applicable if you chose another infrastructure provider.

Continue reading

Get Hugo to render (nice) Asciidocs

2019-02-17 5 min read anoff
While migrating my blog from Jekyll to Hugo I went down quite a rabbit hole. While setup and migration to Hugo was a breeze, I spent a lot of time making my .adoc formatted post work with the new blog. After working through several GitHub issues I ended up manipulating the DOM with Javascript to get admonitions working. It still doesn’t feel right - but hey it works! 🤷‍♂️ This post will cover the steps I took in case I myself or anyone out there ever needs to do this again. Continue reading

Converting existing AsciiDoc into an Antora project

2019-02-15 7 min read anoff
After 2 years of working with the arc42 template in markdown, I spent the last few weeks learning about an asciidoc based site generator named Antora. The main reason for the interest in AsciiDoc was the fact that the limited feature set in markdown often impairs you while writing down complex things. But I had one problem; most of our documentation is scattered across multiple repositories as we try to keep the docs as close to the code as possible. Continue reading

What I learned about Management and Recruiting

2019-01-31 7 min read anoff

As promised my book series will start with two books out of the FAANG league. The book How Google works covers many aspects of the companies culture. The book has lots of management insights that - at the time of reading - were of little interest to me. Therefore I want to briefly highlight a few points about innovation that I find fascinating and then move to the human factors like recruiting and competence management that are also covered extensively in the book.

Continue reading

My year 2018 in books

2019-01-15 8 min read anoff
For the last ~15 months I spent a lot of wasted time (commuting to work, waiting at the doctors, sitting on planes) with audio books. I have never been a big reader and the entry barrier to start listening to a book instead of music is a lot lower than packing books, reading instead of listening etc. That’s why I became a really big fan of listening to various audiobooks. Continue reading

RBAC with Google Firestore

2018-08-12 7 min read anoff

This post will explain how to implement role based access control (RBAC) using the Google Firestore serverless database. Firebase and Firestore in particular with the concept presented in this post offers the most seamless integration of serverless infrastructure with a mobile client at this point. It has become my go to backend for all minor web apps I build.

Continue reading

Markdown native diagrams with PlantUML

2018-07-31 9 min read anoff
This post covers PlantUML basics and how it can be used in GitLab or GitHub projects as well as a seamless local development environment using Visual Studio Code. I have been wanting to write this post for months. Lately I have been using PlantUML extensively at work but also in my private projects. You can see it being used in my plantbuddy and techradar projects on GitHub. Using it in different places and for various purposes I came across a bunch of issues that I want to share in this post. Continue reading

Building realtime apps with Vue and nodeJS

2018-04-18 7 min read anoff
Wanting to build a simple SPA as a side project I struggled with things that might annoy a lot of newcomers that do not want to go full vanilla. Which web framework, which style library, which server framework - and more importantly how does it all work together? In this post we will put together a bunch of great tools out there to build a realtime web app with a few single lines of code. Continue reading
Older posts