Books
type: book: chapter numbering, numbered figures and tables, cross-references, generated indexes and whole-book print.A book is a content tree of type: book: the directory decides chapter order,
front matter decides chapter numbers, and figures, tables, equations and
examples each carry a hand-written number and a stable anchor. Cross-references
resolve in all four outputs, and the book’s root page can generate a
whole-book print HTML.
Two prerequisites: the site’s markup.goldmark has attribute lines and
passthrough enabled (see Components); and
params.ui.shell_types still contains book (the theme default includes it).
A book’s directory
The book root is an ordinary Hugo section, chapters are its subdirectories, and sections are the pages inside a chapter. There is no second chapter list: the sidebar, the pager and the generated contents all read this one tree.
content/handbook/, one book
content/handbook/
- _index.mdbook home: type: book + cascade, holding book-toc and the indexes
ch01/
- _index.mdchapter 1 front page: book_number: 1
- install.mdsection 1.x
- bootstrap.md
ch02/
- _index.mdchapter 2: numbered with book_number, optionally marked draft
- replication.md
- failover.md
- appendix.mdan unnumbered appendix, still in the sidebar and the reading order
Chapter numbers are written by hand: book_number displays exactly what you
write, and the theme never numbers by directory order. The num on a figure,
table, equation or example works the same way — a string the author controls
(2-1, 5.3 and A-2 are all valid), not an index computed at render time.
Rearranging the tree therefore never shifts a number that has already been
printed.
The book home and chapter pages
The book root declares the type, cascades it to descendants, and explicitly
requests the print output. That aggregate is expensive to build, so the theme
does not turn it on for a consuming site:
A book that is a section maps to Hugo’s section output kind; home applies
only when the book sits at the site root:
A chapter page needs only its number and its order:
book_number appears before the page title, in the sidebar and in the generated
contents. book_status: draft is a visible editorial label and does not change
Hugo’s publication state: a draft chapter builds and publishes as usual.
sidebar_headings accepts false, true (h2 only) or a maximum level from 2
to 4. Give every heading that will be referenced an explicit ID, such as
## Synchronous replication {#sync-replication}: a generated slug is fine for
navigation and unfit as a long-lived reference target.
The full key definitions are in Configuration and Page parameters.
Numbering: the native form
Each of the four numbered kinds has a native form: one Markdown block followed
immediately by an attribute line. On that line num= is the number, #id is
the anchor, and caption= is a plain-text caption.
Figures
An attribute line follows the image block. Omitting #id defaults it to
fig-<num>.

The native figure form requires the site to set
markup.goldmark.parser.wrapStandAloneImageWithinParagraph: false; otherwise
the attribute line attaches to the paragraph and is ignored. The alternative
text comes from the Markdown image itself and is never replaced by the caption.
Tables
An attribute line follows a pipe table, and the default ID is tbl-<num>.
| Isolation level | Dirty read | Non-repeatable read | Phantom read |
|---|---|---|---|
| Read Committed | Not possible | Possible | Possible |
| Repeatable Read | Not possible | Not possible | Possible |
| Serializable | Not possible | Not possible | Not possible |
Equations
An attribute line follows a $$ block, and the default ID is eq-<num>. The
number and caption sit on one non-wrapping line to the right of the formula, so
a long caption squeezes the formula column until it becomes a horizontally
scrolling region. Keep an equation caption short.
The native form depends on the site enabling Goldmark passthrough. Without it,
use the eq shortcode below, which goes through local server-side KaTeX.
Examples
A code fence with num= and caption= is a numbered example, and the default
ID is eg-<num>. An #id written on the fence names the enclosing <figure> —
the reference target — rather than the code block itself. The caption is
required: writing only num or only caption fails the build. A numbered
example renders as one framed unit: the caption is the frame’s header and the
body sits inside it, and a body that is exactly one code block sits flush
against the frame instead of drawing a second border.
Numbering: the shortcode form
The four shortcodes fig, tbl, eq and eg render a <figure> identical to
the native form, register into the same target table, and sort by source
position. Use them only where the native form cannot reach: an image that needs
an outbound link, several tables under one number, a site without passthrough,
or an example body made of several fences and prose.
fig takes src= (it also accepts inner Markdown content, and the two are
mutually exclusive) and additionally supports link, alt, width, height,
class, and the migration alias title:
tbl wraps the label, the table, the caption and the anchor in one semantic
figure:
| Output | Label | Anchor |
|---|---|---|
| HTML | Visible | Stable |
| Visible | Stable |
eq hands its content to local server-side KaTeX, so it does not depend on
passthrough:
A bare {{< eq >}} with no parameters is the unnumbered display-maths
escape hatch: it registers no target, cannot be reached by xref, and does not
appear in the equation index.
eg is a wrapping shortcode whose body renders under the page’s Markdown
policy, usually holding one or more fences:
IDs must be unique within a page, and within one kind a number maps to exactly one ID. A duplicate fails the build, and the error names the line that claimed it first.
Hugo renders a shortcode body as its own Goldmark document, and footnotes are
page-level. A [^label] inside the body of tbl, eg, fig, card, tab,
field or include fails the build, naming the file, the line and the label.
With the definition on the page, the reference would print literally as
[^label]; with the definition in the body, it would build a second footnote
list whose fn:N ids collide with the page’s own. Neither belongs in
published output.
A table or code block that needs footnotes uses the native form instead: a
table, image or fence carrying {num=… caption=…} keeps its content in the
page document, where a footnote numbers, links and backlinks like any other.
The rendered figure is the same either way, so this is usually a one-line
change. Footnote-shaped text in code — a [^0-9] character class in a
listing, or a code span — is left alone.
Cross-references
A target on the same page can be reached with a plain Markdown link: Table 2-1 points at the isolation table above. The cost is that the label and the number are hand-written, so changing a number means finding them yourself.
xref composes the label, the number and the anchor in one place, and works
across pages and languages:
See Figure 2-2 and Example 2-1; with an explicit anchor: Figure 2-1.
The rules:
- At most one kind key (
fig,tbl,eq,eg). The kind supplies the localized label (Figure / Table / Equation / Example) and derives the default anchor<kind>-<num>. anchor=overrides the derived anchor, for a target that wrote an explicit#id.page=references another page through Hugo’s page lookup in the current language, so the source never hard-codes a/zh/prefix.- Without a kind, both
anchor=and inner link text are required:{{< xref page="../ch01/install" anchor="sync-replication" >}}synchronous replication{{< /xref >}}. - A reference may precede its target: nothing reads the registry at render time, so forward references are valid.
A plain cross-page Markdown link is still a site URL inside the whole-book
print. A reference that must also jump within the aggregate document is written
as an xref.
Indexes: contents and lists of figures
Five index shortcodes walk the same book tree, triggering descendant content and
aggregating what it registered. They usually sit on the book home
(_index.md) or on a dedicated “list of figures” page.
These five appear here as source only. They walk down from the navigation root
the current page belongs to, so placing one in an ordinary documentation tree
would list the whole docs tree as a book. For the real effect, read
Write Beautiful Docs and inspect its
content/book/_index.md
source.
book-toctakes adepthof 1 to 3: 1 lists chapters, 2 adds nested sections, 3 also projects each page’s heading tree.drafts=falsefiltersbook_status: draftrows out of this generated list only, and does not affect publication.book-figures,book-tables,book-equationsandbook-examplestake no parameters. Each lists one kind, with entries like “Figure 2-1 — caption” linked to the stable ID.- In whole-book print, all of these links become in-document fragments.
Sequential reading and drafts
The pager is on by default for the docs, book and blog types, and its
order is a pre-order walk of the sidebar tree: a section index first, then its
children by weight. Turn a whole type off with params.ui.pager_types, and a
single page off with pager: false.
Entries hidden with toc_hide, manual_link link-only placeholders and
sidebar_divider rows never become pager destinations.
Besides the “draft” label in the sidebar, a draft chapter can carry a banner above its body:
The banner appears only on pages that are both type: book and
book_status: draft, and its wording comes from the localization key
book_draft_notice.
Printing the whole book
Once the book root has the print output, it generates a cover, a local table
of contents, the root page’s body and every descendant chapter in visible
reading order, all inside one HTML document. Pages with no_print: true,
link-only nodes, divider rows and hidden placeholders never become chapters.
Inside the aggregate, the IDs of numbered components are preserved byte for
byte. Markdown heading IDs within a page are prefixed with their source page to
avoid collisions when several chapters share an anchor such as summary, and
the generated heading links are rewritten to match. The output is
print-oriented HTML; PDF and EPUB are the site’s own business.
The switches themselves, and per-chapter print, are covered in Print.
Migrating an existing manuscript
An existing manuscript usually expresses figure and table numbering with the
site’s own figure shortcode, bold pseudo-captions, and bare links to #fig_*.
The theme repository ships a migration script that rewrites those legacy forms
into fig, tbl and xref while preserving the public anchors already
published. Pin the site to a released OINK version that includes the Book
components first, then migrate the content.
Four profiles cover the legacy conventions of three real manuscripts (DDIA contributes one each for v1 and v2), and each recognizes only the forms actually observed in them:
--profile |
Legacy form it recognizes |
|---|---|
tpme |
A pseudo-h6 caption beside an image, a caption beside a table, and bare /en/...#fragment links |
ddia-v2 |
The site’s own figure shortcode, classified by number into figure / table / code example |
ddia-v1 |
A bare image with an adjacent bold numbered caption, with the ID derived from the image filename |
pg-internal |
A bold or italic “Figure N” caption in Chinese or English next to an image, and a numbered table caption next to a table |
The diff goes to standard output, the summary to standard error, and the report
carries files_scanned, files_changed, counts, skipped and idempotent.
The script rewrites only targets it can determine uniquely: where the number is
unclear, the caption is not unique, or the marker form is unrecognized, the text
is left as it stands and recorded in skipped for a human. Bold text, inline
code and formulas inside a legacy caption degrade to plain text, because a Book
caption is plain text by contract.
After reviewing the diff, apply it on a dedicated branch and run a second pass to confirm idempotency:
The second report should read files_changed: 0, an empty counts and
idempotent: true; the script signals idempotency with exit code 0.
The profiles recognize only the legacy forms actually observed in those three
manuscripts. Where a manuscript’s conventions fall outside the four, the script
does not apply and the rewrite is manual, following
Numbering: the native form. The theme repository’s
bin/check-book-migrations.py covers all four profiles with a dry-run and an
idempotency check.
Verify
- The build is warning-free:
hugo --printPathWarnings --panicOnWarning. A malformed number, a duplicate ID and a missing caption all fail here. - The page should show a localized label such as “Figure 2-1”, clickable
xreflinks, and anchors that land correctly. - Compare the chapter order across all four places: sidebar, pager,
book-tocand whole-book print. - Check the Markdown output:
curl -s http://localhost:1313/handbook/ch02/index.md. The shortcode form should degrade to**Figure 2-2.** captionplus the original body, and the native form should keep its source block and attribute line as they are. - Run the anchor check from the theme repository against the build output:
It verifies that every reference’s target anchor exists, that kind and number agree, that page-local IDs are unique, and that a numbered image has alternative text worthy of its caption.
Book shortcode parameters
xref:
book-toc:
book-figures, book-tables, book-equations and book-examples take no
parameters.
Limits
- There is no automatic numbering. Chapter, figure and table numbers are all written by hand; changing one is a deliberate edit, not a side effect of a build.
- The attribute line must touch its block, with no blank line between. An attribute line a tool like Prettier has moved fails silently, and the figure degrades to a plain image.
book_kindandbook_partare metadata keys the contract acknowledges but the current templates do not render. The ones with a visible effect arebook_numberandbook_status.- The index shortcodes trigger descendant content rendering, which noticeably lengthens the build on a very large tree. The same reason is why whole-book
printhas to be requested explicitly. - A footnote reference cannot appear in a shortcode body; the build fails and names the native form to use instead — see Numbering: the shortcode form.
- The theme stops at print HTML: pagination, font embedding, index compilation and PDF / EPUB packaging are outside the contract.
Related
- Organizing content — how the tree becomes the sidebar and the reading order
- Images — captions, sizing, zoom and image processing
- Tables — table attribute lines and full-width tables
- Math — KaTeX and passthrough configuration
- Print — per-chapter and whole-book print