If you haven’t used it before, Markdown is a very simple way of indicating style and structure in text.
Markdown heading levels are how you give a book its structure. Book4matter reads them as a hierarchy and supplies the “PART” and “CHAPTER” labels, the page breaks, and the numbering for you — you write only the titles.
Text on a part divider
A part is usually a page to itself with nothing but the title. If you want a few words on that divider page — an epigraph, a sentence framing the part — write them directly under the part heading, before the first chapter:
# Foundations
Everything in this part assumes you can already hold a frame and keep time.
## Posture
...
Because the chapter files are concatenated in order, this “divider text” lives at the top of the part’s own file, ahead of its first ## chapter. It prints beneath the part title on the divider page rather than spilling onto the next.
Chapter 4 Novels and Flat Books
The previous chapter describes the full hierarchy — parts, then chapters, then sections. Many books never use it. A novel is the clearest case: it has chapters, often with no titles at all, just numbers, and no parts above them. Book4matter has a low-friction path for exactly this, so that a book with the least possible structure needs the least possible formatting.
There is no such thing as parts without chapters
A book can have chapters and no parts, but never parts and no chapters. So when a manuscript uses no ## headings anywhere, book4matter reads # as chapter, not part — a plain chaptered book, arabic page numbers from page one, no divider pages. You write # for each chapter title and nothing else. This happens on its own; there is no flag to set.
--no-parts: when you do use ## inside a chapter
Some authors naturally write # for a chapter and ## for a heading within that chapter. Left alone, book4matter would read those as parts and chapters. Pass --no-parts (or set no-parts: true in book_style.yaml) and every heading shifts down one level: # becomes a chapter, ## a section, ### a subsection, and so on. No part dividers are produced.
./run.sh print my-novel/ --no-parts
Files with no heading become numbered chapters
Here is the smallest possible book: a folder of text files with no markup in them at all.
my-novel/
book_metadata.yaml
chapters/
01.txt
02.txt
03.txt
Each file with no heading becomes one chapter, numbered in the order the files are read — the first file is Chapter 1, the next Chapter 2, and so on. You never type the numbers; book4matter supplies them, so inserting or reordering a file renumbers the rest. The chapter opens with a centered numeral over a short rule, the classic novel look.
Because nothing in the book carries a title, there is nothing to list, so the Contents page is left out automatically. (Set toc: true in book_style.yaml if you want one anyway; the numbered chapters then appear as “Chapter 1”, “Chapter 2”, and so on.)
Plain text files
Chapters may be .txt as well as .md. A .txt file is pure text: Markdown headings, emphasis, links, and scene-break markers appear literally. Blank lines separate paragraphs. Each file becomes an automatically numbered chapter in PDF/print builds. Use .md when you want Markdown formatting.
A named section among numbered chapters
Most novels want at least a titled page or two — a prologue, an introduction, an afterword. Give that one file a # heading and leave the rest without one:
chapters/
00-introduction.md # begins with "# Introduction"
01.txt # no heading
02.txt # no heading
99-afterword.md # begins with "# Afterword"
A titled file set among untitled ones is treated as a named section: it shows its title (Introduction, Afterword) and is not given a chapter number, nor does it disturb the count. The untitled files remain Chapter 1, Chapter 2. Because the book now has titles, a Contents page is included, listing the named sections alongside the numbered chapters.
If instead every file has a # title, they are all ordinary titled chapters, numbered straight through — the titled-chapter novel, or any chaptered non-fiction book built with --no-parts.
The bundled novel
The repository ships a second example, novel/, alongside the example/ book. It is a very short fable with no titles, no parts, and no markup — just numbered .txt chapters — so you can see the whole of this chapter at work:
# Writes novel/out/the-smallest-fire-interior.pdf.
./run.sh print novel/
Chapter 7 Auto-Numbering, Cross-References, and Lists
Books are full of things that are numbered and referred to: exercises, figures, charts, tables. Numbering them by hand is a trap — insert one new figure early on and every later number, and every reference to it, is suddenly wrong. Book4matter numbers these tracked items for you, lets you refer to them by a name you choose (never by number), and can gather them into a list — a List of Exercises, a List of Figures — with the correct page beside each.
Register the kinds you use
First tell the book which kinds of thing you track, in book_metadata.yaml. Each kind gets a label: the word printed before the number and heading its list.
tracked:
exercise: { label: "Exercise" }
figure: { label: "Figure" }
chart: { label: "Chart" }
table: { label: "Table" }
Registering the kinds up front is what keeps the feature safe: only these words are treated as tracked-item markup, so an ordinary {ratio: 3:2} written in your prose is left completely alone. A kind is lowercase letters, digits, or hyphens.
Define an item where it belongs
Give each item a short id — a name that means something to you, unique within the whole book. Two ways to write a definition:
{exercise:pushups:def}
{exercise:pushups Push-ups to failure}
The first defines an exercise with the id pushups and no title. The second does the same but adds a title: anything after the first space is the title. Both print in place as Exercise 3 (whatever the next number is); the title is not printed here — it is saved for the list at the back. Write any caption text around the token yourself:
{figure:rotary A rotary telephone}. *An early rotary dial, c. 1950.*
For a figure or chart, put the definition in the caption beside the image; for an exercise, at the head of the exercise. Each kind counts on its own, so your first figure is Figure 1 even if three exercises came before it.
Refer to an item by name
Anywhere else — before or after the definition — refer to the item with just its kind and id, no :def:
Warm up before you attempt {exercise:pushups}.
That prints Exercise 3, and the number is always right because the build looks it up. What the reference becomes depends on the output, exactly like any other internal link:
- In digital PDF, EPUB, and HTML it is a live, clickable link to the item.
- In print there are no clickable links (KDP forbids them), so it prints the number followed by the page, as Exercise 3 (page 42) — resolved for you and kept correct as the book repaginates.
Because you never type a number, inserting, removing, or reordering items is free: every number and every page updates on the next build.
List them at the back (or the front)
Drop this on a line of its own wherever you want the list to appear:
{index:exercise}
It prints every exercise in order of appearance, each one a link to the item. In the print and digital PDF outputs each entry carries its page number, like a table of contents for your exercises; in EPUB and HTML, which have no fixed pages, the entry is simply a tappable link. Give it a heading of its own:
# List of Exercises {.section}
{index:exercise}
By book convention a List of Figures or List of Tables sits in the front matter, just after the table of contents, while an alphabetical index of terms goes in the back. Book4matter does not force either: the list appears wherever you place {index:...}, and the page numbers resolve correctly whether the content it points to comes before or after it.
When something is wrong, the build says so
The system fails loudly rather than printing a quietly wrong book:
- A reference to an id that is never defined stops the build and names it — usually a typo in the id, or a definition you forgot to mark with
:def.
- Defining the same id twice for one kind stops the build.
- An unregistered kind in an unmistakable token —
{exercise:x:def} or {index:exercise} when exercise isn’t in your tracked: map — stops the build. This is what catches the commonest mistake: forgetting the tracked: block altogether, or mistyping a kind ({index:figurse}).
The two unmistakable forms are the :def keyword and {index:...}; they can only be tracked markup, so an unregistered kind there is always an error. The plainer forms overlap with ordinary writing, so {exercise:pushups} or {exercise:pushups A title} with an unregistered kind is left as text and prints verbatim (you will see it and can fix it) rather than being seized from your prose. Either way, tokens inside code spans and fenced code blocks are never touched — which is how this chapter shows them to you.
Numbering is book-wide, so the one output that cannot do it is a single-chapter HTML fragment (html chapter N): with only one chapter in hand there is no way to number across the book or resolve a reference to another chapter, so tracked tokens are left as-is there. Every whole-book output — print, PDF, EPUB, and the whole-book HTML page — handles them fully.
An alphabetical index of terms
Mark a definition by surrounding its words with double brackets:
Let's talk about [[practice hold]] in [[Rotary Waltz]].
The sentence prints as ordinary text, with no brackets, numbering, or special styling. Only marked definitions enter the index; other mentions are not collected automatically. No tracked: registration is needed for terms. The name term is reserved for this built-in index.
Place the index on its own line, under a heading of your choice:
# Index {.section}
{index:term}
Entries sort alphabetically without regard to case. Repeated definitions of the same term share an entry, using the spelling of the first definition. In print and PDF the entry lists its page numbers, with each page listed only once. Digital PDF page numbers link to their definitions; print has no hyperlinks. HTML and EPUB link the term to its first definition, with numbered links to additional definitions. The index may appear before or after the definitions.
Every definition has a separate term-based anchor, such as idx-frame-1 or idx-practice-hold-1. Additional definitions with the same slug increment the suffix. Heading definitions retain their own index anchors on the term itself. The build removes duplicate index IDs from generated navigation, keeping the definition as the unique target and its heading visible when following a link. Ordinary heading links continue to use the heading’s original anchor. In print and PDF, the contents renders the heading text without its index marker. A repeated definition marker stops the build with the term and anchor ID in the error, rather than silently indexing a contents-page copy. Separate intentional definitions of the same term still have distinct IDs and are valid. HTML and EPUB also validate index anchors after navigation cleanup: a duplicate ID or any definition ID left in navigation stops the build, naming the anchor, generated file, and line numbers. EPUB checks across chapter files as well as within each file, even when EPUBCheck is disabled with --no-check.
Markers must contain plain text; place emphasis around the entire marker if needed (*[[practice hold]]*). Markers in code spans and code blocks stay literal. Reference (:ref) syntax is not supported yet. Single-chapter HTML fragments leave markers literal, like other tracked markup.
Indexing text that is already a link
Put the double-bracket definition inside the ordinary Markdown link text:
I started taking classes with Stanford University's [[[Richard Powers]]](https://www.richardpowers.com).
The three opening brackets are intentional: the outer [ starts the link text, and the inner [[ starts the index definition. The closing brackets nest the same way, followed by the link destination in parentheses. No spaces, escapes, or bold markup are needed.
The name remains an ordinary outbound link, with the same styling as other links. The Richard Powers entry in {index:term} links back to the name’s location in the book; clicking the name in the prose follows the external URL. This combines an outbound link and an index definition without creating nested links: the index definition supplies an anchor inside the outbound link.
Using a different name in the index
Separate the visible text from the index entry with |:
I started taking classes with [[Richard Powers|Powers, Richard]].
The prose shows Richard Powers, while the index lists Powers, Richard under P. Both sides are plain text; spaces around the separator are trimmed. The index entry determines grouping, alphabetical sorting, and the anchor name (for example, idx-powers-richard-1). Different visible names with the same index entry are collected together, just like repeated ordinary definitions.
This also works inside an outbound link, without adding bold formatting:
I started taking classes with Stanford University's [[[Richard Powers|Powers, Richard]]](https://www.richardpowers.com).
Clicking the name in the prose opens the external website; clicking Powers, Richard in the index returns to this passage. In print and PDF, the index entry has the definition’s page number as usual. Use exactly one | and provide nonempty text on both sides. Existing [[term]] definitions work unchanged, using the same words in the prose and index.
Grouping terms with subentries
Use > in the index wording to separate a parent entry from a subentry:
[[Practice hold]]
[[polka hold|Practice hold>polka]]
The second marker displays polka hold in the prose. The index groups it under Practice hold, with an indented polka entry:
Practice hold ................ 22
> polka .................. 23
Use | to separate the visible words from the index wording, as with other aliases. Spaces around > are optional and trimmed. The parent and its subentries sort alphabetically without regard to case; repeated definitions of the same subentry share one entry, with duplicate page numbers removed. Subentries with the same name under different parents remain separate.
If only a subentry is marked, its parent is created automatically. A parent gets its own page numbers or definition links only from explicit parent markers such as [[Practice hold]]; it does not collect its children’s pages. In HTML and EPUB, children link to their own definitions, and an automatically created parent without a definition is plain text.
Subentries also work in headings and outbound links:
## [[Polka practice hold|Practice hold>polka]]
[[[polka hold|Practice hold>polka]]](https://example.com)
The heading keeps its normal appearance and the link still opens the external website. The index links to the marked term in the book. Each definition has its own anchor, such as idx-practice-hold-polka-1, excluded from the contents in every format.
Index rows have dot leaders between the term and its locations, and every subentry has a > marker as well as indentation. In HTML and EPUB, the numbers on the right are linked occurrence numbers (1, 2, and so on), not fixed page numbers. The column is labelled Refs; each number opens an indexed occurrence. The term itself also links to the first definition. Automatically created parents without definitions have no leaders or location numbers.
Print and PDF keep each parent with its first child. If a group spans pages, the parent repeats as Practice hold (continued) above the children on each subsequent page. The continuation heading does not repeat the parent’s page numbers or create a new index definition. Long child rows stay together.
One level of subentries is supported. Both parent and child must be nonempty; [[text|Parent>]], [[text|>child]], and [[text|A>B>C]] stop the build with a markup error. Commas remain ordinary index text, as in Powers, Richard.
Adding “see” aliases
Add alternate lookup names after the main index wording, separated by ;:
[[Tempo|Tempo;BPM]]
The prose displays Tempo. The index contains a normal Tempo entry with its locations, plus BPM. see Tempo under B when BPM has no indexed occurrences of its own. That “see” entry has no page numbers or dot leaders. In digital PDF, HTML, and EPUB, both “see” and “see also” link to the target’s row in the index, where readers can choose among all its locations. A target with subentries links to the specific subentry’s row. Print shows plain text.
You can declare several aliases on one definition:
[[Tempo|Tempo;BPM;Beats per minute]]
The visible wording can differ, and aliases also work in headings and links:
## [[Musical speed|Tempo;BPM]]
[[[Tempo|Tempo;BPM]]](https://example.com)
An alias can point to a subentry:
[[polka hold|Practice hold>polka;Polka grip]]
That adds Polka grip. see Practice hold, polka, linking to the polka hold definition. Alias names themselves are top-level entries, not subentries.
If the alias also has indexed occurrences, the index keeps those locations and adds see also automatically:
[[Tempo|Tempo;BPM]]
[[BPM]]
[[BPM]]
Here BPM lists the locations of both [[BPM]] markers, followed by see also Tempo. Tempo lists the location of the first marker. If BPM has subentries, those are kept too. You can add occurrences anywhere in the book, before or after the alias declaration; no markup changes are needed. Ordinary, unmarked mentions of BPM do not add index locations or change “see” to “see also”.
Spaces around ; are trimmed. Repeating the same alias for the same entry is allowed and produces one cross-reference. An alias cannot be empty, refer to itself, or point to different entries. Names are compared without regard to case. Invalid aliases stop the build with a source diagnostic. In index wording after |, ; is reserved for alias separators; commas remain ordinary text, as in Powers, Richard.
Finding markup errors
Invalid term definitions stop the build with a reason, the original chapter path and line number, and the offending marker. For example:
tracked.lua: term definitions require nonempty display text and index entry
at /path/to/book/chapters/010-introduction.md:12
offending markup: [[Richard Powers|]]
Print diagnostics refer to the chapter you edit, rather than a temporary file created during preprocessing. If identical markup occurs in several places, the error lists matching source locations instead of guessing one. For input from standard input or markup transformed by Markdown parsing, a source location may be unavailable; the message still includes the offending marker.