mdBook's plugin model: a closer look
mdBook, the Rust-based tool for generating books from Markdown, is widely known for publishing the official Rust book. Less discussed is its surprisingly clean plugin architecture, which lets you inject arbitrary programmatic transformations into the book-building pipeline using any language you like.
mdBook's rendering pipeline is straightforward: a directory tree of Markdown files is loaded, processed, and rendered into output (HTML by default, though other formats are possible). The preprocessor mechanism sits between the loading and rendering stages—a registered external program receives the book's source data and can modify it before the renderer ever sees it.
Two reference implementations
To see this in action, consider the narcissist plugin—a trivial preprocessor that demonstrates the core mechanics. The reference code ships in two flavors:
- A Python implementation, showing that preprocessors can be written in any executable language.
- A Rust implementation, illustrating the richer application API mdBook exposes to plugins written in its native language.
How the mechanism maps to plugin concepts
Discovery
Discovery is deliberately explicit. Each preprocessor must be listed in the project's book.toml configuration file:
[preprocessor.narcissistpy] command = "python3 ../preprocessor-python-narcissist/narcissist.py"
Each listed preprocessor is simply a command that mdBook executes as a sub-process—Python in this case, but any executable would serve.
Registration and invocation
Registration involves running the plugin command twice. The first invocation passes supports <renderer> with the renderer's name (e.g., html) as an argument; a zero exit code signals support for that renderer. The second invocation feeds the entire book as JSON via stdin, plus context metadata, and expects the preprocessor to emit the modified book as JSON on stdout.
Hooks: coarse-grained by design
The hook design is deliberately coarse. A preprocessor receives the full book as a single JSON object and returns the full updated book the same way. It's up to the plugin to decide which chapters to read or alter.
This is a reasonable trade-off for typical documentation sizes—even tens of MiB of JSON move quickly through stdout between sub-processes. But it would not scale to something like a wiki-scale corpus, where finer-grained hooks would be needed.
Application API for Rust plugins
Since the preprocessor protocol is language-agnostic, the richer API is reserved for Rust implementations. Rust preprocessors can use mdBook's Preprocessor trait and built-in JSON unmarshalling utilities, which simplify traversing the book's structure. The Rust narcissist example shows the trait in practice.
Renderers: the sibling mechanism
mdBook actually has a second plugin type: renderers, sometimes called backends. They consume exactly the same JSON-encoded book data as preprocessors, but instead of handing back a modified book, they're free to do anything with the data—the default renderer produces HTML, while third-party renderers target other outputs. A book can pass through any number of preprocessors, but ends at a single renderer.
Because renderers receive identical input to preprocessors, the architectural patterns discussed here apply to both mechanisms equally.



