Skip to content
Grimoire

Configuration

grimoire.toml sits next to the book, and every key in it is optional - a directory of Markdown files builds with no configuration file at all. Point --config elsewhere to use a different one.

Two rules hold throughout:

  • A command-line flag wins over the file, so a one-off build needs no edit.
  • A key of the wrong type keeps its default rather than failing the build, and an unknown key is ignored. A half-written table degrades to the defaults instead of aborting a build that would otherwise have succeeded.

The whole file

Every key, with its default:

toml
[book]
title = "Documentation"
description = ""
authors = []
authorsLabel = "Written by"
language = "en"
src = "docs"

[build]
out = "site"
jobs = 0                # 0 = one render task per CPU

[html]
theme = "grimoire"
mode = "auto"           # auto | light | dark
tocDepth = 3
sectionNumbers = true
footer = "Rendered with <a href=\"...\">Grimoire</a>"
repoUrl = ""
repoLabel = "Source"
editUrl = ""
favicon = ""
logo = ""

[highlight]
enabled = false

[highlightjs]
enabled = false
cdn = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1"
style = "github"
styleDark = "github-dark"
languages = ["bash", "go", "json", "yaml", "xml", "ini", "nginx"]

[search]
enabled = true
bodyChars = 1200

[pdf]
enabled = false
output = "book.pdf"
paper = "a4"            # a4 | letter
bookmarkLevel = 3
pageNumbers = false
footerLeft = ""
titlePage = true

[book]

KeyTypeDefaultMeaning
titlestring"Documentation"shown in the sidebar and in every page title; also the PDF cover and Title
descriptionstring""emitted as a description meta tag, and as the PDF Subject
authorslist of string[]the author meta tag, the PDF Author field, and the reader-facing credit
authorsLabelstring"Written by"what introduces those names where a reader sees them; "" prints them alone
languagestring"en"the BCP 47 tag on the html element
srcstring"docs"the directory of Markdown to build, relative to the working directory

src is where the outline comes from. If it holds a SUMMARY.md, that file is the outline; if not, the directory tree is walked instead.

authors is used two ways, and only one of them is labelled. The author meta tag and the PDF Author field want the names alone, because those are read by software. The page footer and the PDF cover want a credit, because a name standing on its own says nothing about what it is - so authorsLabel goes in front of it:

toml
authors = ["Ada Lovelace", "Grace Hopper"]
authorsLabel = "Built with love by"   # -> Built with love by Ada Lovelace, Grace Hopper
authorsLabel = ""                     # -> Ada Lovelace, Grace Hopper

[build]

KeyTypeDefaultMeaning
outstring"site"where the site is written
jobsint0chapters rendered in parallel; 0 means one task per CPU

The output directory is created if it is missing. Files already there are left alone unless the build writes over them, so an unrelated file in site/ survives - but so does a chapter you deleted from the book, which is worth knowing before publishing.

jobs above the chapter count simply idles the extra workers. See Performance for what raising it actually buys.

[html]

KeyTypeDefaultMeaning
themestring"grimoire"one of the ten themes; an unknown name falls back to grimoire
modestring"auto"the colour mode a first-time reader gets: auto, light, dark
tocDepthint3deepest heading level in the per-page contents; clamped to 1-6
sectionNumbersbooltruenumber the chapters in the sidebar
footerstringthe Grimoire creditHTML placed in the page footer; "" for no footer
repoUrlstring""a source-repository link in the top bar; "" for none
repoLabelstring"Source"the label on that link
editUrlstring""an edit-this-page URL with a {path} slot; "" for none
faviconstring""a favicon path, copied into the site
logostring""a logo shown beside the title, relative to src

mode only decides the first visit. Once a reader touches the selector, their choice is remembered and this setting no longer applies to them. Whatever it resolves to is stamped on the document before the first paint, so navigating a dark site never flashes white.

editUrl substitutes the chapter's source path for {path}:

toml
editUrl = "https://github.com/me/book/edit/main/docs/{path}"

logo pointing at an SVG gets inlined into the page, so it inherits the colour mode through currentColor and costs no extra request; any other format is linked as an img. The path is resolved against src, and ../ out of it is fine:

toml
logo = "../assets/wordmark.svg"

footer is emitted verbatim, so it can carry HTML:

toml
footer = "&copy; 2026 Example Ltd &middot; <a href=\"/imprint\">Imprint</a>"

That is deliberate - the footer is your own configuration, not untrusted input. Everything else that renders text, including the book and chapter titles, goes through the Markdown renderer and is escaped.

[highlight] and [highlightjs]

Highlighting comes in two layers, and they are two tables because they have very different consequences.

[highlight] is the switch; [highlightjs] describes the CDN layer, so every key that only means something to highlight.js lives there.

KeyTypeDefaultMeaning
highlight.enabledboolfalsethe master switch. On its own: the built-in Jennifer highlighter
highlightjs.enabledboolfalseadditionally load highlight.js from a CDN
highlightjs.cdnstringcdnjs 11.11.1the CDN base URL, with no trailing slash
highlightjs.stylestring"github"the highlight.js stylesheet used in light mode
highlightjs.styleDarkstring"github-dark"the stylesheet used in dark mode
highlightjs.languageslist of string["bash", "go", "json", "yaml", "xml", "ini", "nginx"]extra highlight.js language packs to load

[highlight] alone highlights Jennifer code while the site is built. The spans are in the HTML that gets written, so there is no CDN, no JavaScript, and nothing to load; it works with scripting off and over file://. This is the layer to enable for a book that must make no third-party requests.

[highlightjs] on top pulls highlight.js from cdn and highlights the languages the built-in highlighter does not know, loading the languages packs and swapping style and styleDark with the mode selector. Blocks Grimoire already highlighted are marked so highlight.js leaves them alone.

With highlightjs.enabled = false, the other three keys do nothing - there is no stylesheet to choose and no grammar to fetch - which is exactly why they sit in this table rather than beside the master switch.

highlight.enabled is the master switch:

highlighthighlightjsResult
offoffno highlighting (the default)
onoffJennifer, at build time; nothing fetched
ononJennifer at build time, everything else from the CDN
offonno highlighting, and the build says so

The last row is a contradiction rather than an intent, and it resolves to off: a book that says "no highlighting" should not start making third-party requests because a second table was left enabled. The build reports the combination on stderr instead of silently picking one of the two readings.

The default CDN URL pins a version rather than tracking latest. A documentation build should render the same today and in a year, and a silent major-version bump on a CDN is exactly the kind of change that breaks a language grammar.

KeyTypeDefaultMeaning
enabledbooltruebuild the search index and ship the search UI
bodyCharsint1200body text kept per indexed section; floored at 120

Indexing is per section, not per page, so a hit lands on the paragraph rather than at the top of a long chapter. bodyChars trades index size against how deep into a section a match can still be found. --no-search turns it off for a single build without touching the file.

[pdf]

KeyTypeDefaultMeaning
enabledboolfalsegrimoire build also renders the PDF
outputstring"book.pdf"the PDF path, relative to build.out
paperstring"a4"a4 or letter
bookmarkLevelint3bookmark headings down to this level; 0 disables the outline
pageNumbersboolfalseprint page/total at the outside edge of every page footer
footerLeftstring""a template for the other side of that footer; "" leaves it empty
titlePagebooltrueopen the book with a title page; off starts it at the first chapter

enabled is what --pdf sets, and grimoire pdf renders the PDF regardless of it. Expect the PDF to dominate the build - see Performance.

pageNumbers needs the total page count, which does not exist until the whole book has been laid out, so the build renders the document and then stamps the footer on every page. The number is drawn inside the bottom margin in the theme's muted colour - a footer is for placing yourself, not for reading.

footerLeft fills the other side, and carries two slots read from the book's own git checkout:

{version}the tag the build sits on, with any leading v removed
{commit}the short commit id - only when there is no tag

Exactly one of the two is ever filled, which is what lets a single template cover a release and a working build. This manual uses:

toml
footerLeft = "Grimoire {version} Manual {commit}"

giving Grimoire 1.0.0 Manual on a tagged commit and Grimoire Manual 0e173c1 on anything else. The gap the empty slot leaves is closed before the line is drawn. Outside a git checkout - or where git is unavailable, including on jennifer-tiny, which ships no os/exec - both slots come back empty and the rest of the template still prints.

titlePage off drops the cover entirely and starts the PDF at the first chapter, for a book that would rather supply its own front matter as a prefix chapter.

A worked example

The grimoire.toml in this repository builds these pages - Grimoire renders its own documentation:

toml
[book]
title = "Grimoire"
description = "Build a documentation website, and a printable PDF, from a directory of Markdown files."
authors = ["mplx <jennifer@mplx.dev>"]
authorsLabel = "Written by"
language = "en"
src = "docs"

[build]
out = "site"

[html]
theme = "grimoire"
mode = "auto"
footer = 'Rendered with <a href="https://grimoire.jennifer-lang.dev/">Grimoire</a>, by itself.'
repoUrl = "https://github.com/jennifer-language/grimoire"
repoLabel = "Source"
editUrl = "https://github.com/jennifer-language/grimoire/edit/main/docs/{path}"
favicon = "favicon.ico"

[highlight]
enabled = true

[pdf]
enabled = true
output = "grimoire.pdf"
paper = "a4"
pageNumbers = true
footerLeft = "Grimoire {version} Manual {commit}"
titlePage = true

Note the single-quoted footer: TOML's literal strings take no escapes, which makes an HTML attribute far easier to write than the \" of a basic string.