---
title: "Preprocessors"
documentId: "mdbook:guide/src/for_developers/preprocessors.md"
order: 29
licenseSource: "mdbook-guide"
documentContext: [{"kind": "source", "html": "<p>mdBook 0.5.4 fixed documentation, MPL-2.0. <a href=\"https://github.com/rust-lang/mdBook/blob/2ea30c00f00647d2b3f4c0f79b3e0e1eabc0b66d/guide/src/for_developers/preprocessors.md\">Original chapter</a> · <a href=\"/docs/mdbook/source/v0-5-4/original/guide/src/for_developers/preprocessors.md\">Original Markdown</a> · <a href=\"/docs/mdbook/source/v0-5-4/edited/en/13-for_developers-preprocessors.md\">Editable Libx document</a> · <a href=\"/docs/mdbook/source/v0-5-4/mdbook-original.tar.gz\">Complete fixed upstream source</a>. · <a href=\"/docs/mdbook/source/v0-5-4/source.zip\">Editable source and build context</a>.</p>"}, {"kind": "editorial", "html": "<p>Static Libx presentation: examples show their complete code, including lines hidden in the original demo. Editing, code execution and MathJax rendering are provided by the <a href=\"https://github.com/rust-lang/mdBook/blob/2ea30c00f00647d2b3f4c0f79b3e0e1eabc0b66d/guide/src/for_developers/preprocessors.md\">original project</a>; formula examples retain their original TeX notation here. The document text and expanded examples are retained. This is an unofficial Libx static edition. Original text and examples are preserved; translated pages are identified separately.</p>"}]
prev: {"text": "For developers", "link": "/v0-5-4/en/01-guide/11-for_developers-index"}
next: {"text": "Alternative backends", "link": "/v0-5-4/en/01-guide/12-for_developers-backends"}
---


<div class="mdbook-guide">
<h1 id="preprocessors"><a class="header" href="#preprocessors">Preprocessors</a></h1>
<p>A <em>preprocessor</em> is simply a bit of code which gets run immediately after the
book is loaded and before it gets rendered, allowing you to update and mutate
the book. Possible use cases are:</p>
<ul>
<li>Creating custom helpers like <code>{{#include /path/to/file.md}}</code></li>
<li>Substituting in latex-style expressions (<code>$$ \frac{1}{3} $$</code>) with their
mathjax equivalents</li>
</ul>
<p>See <a href="/docs/mdbook/v0-5-4/en/01-guide/18-format-configuration-preprocessors/">Configuring Preprocessors</a> for more information about using preprocessors.</p>
<h2 id="hooking-into-mdbook"><a class="header" href="#hooking-into-mdbook">Hooking into MDBook</a></h2>
<p>MDBook uses a fairly simple mechanism for discovering third party plugins.
A new table is added to <code>book.toml</code> (e.g. <code>&#91;preprocessor.foo&#93;</code> for the <code>foo</code>
preprocessor) and then <code>mdbook</code> will try to invoke the <code>mdbook-foo</code> program as
part of the build process.</p>
<p>Once the preprocessor has been defined and the build process starts, mdBook executes the command defined in the <code>preprocessor.foo.command</code> key twice.
The first time it runs the preprocessor to determine if it supports the given renderer.
mdBook passes two arguments to the process: the first argument is the string <code>supports</code> and the second argument is the renderer name.
The preprocessor should exit with a status code 0 if it supports the given renderer, or return a non-zero exit code if it does not.</p>
<p>If the preprocessor supports the renderer, then mdbook runs it a second time, passing JSON data into stdin.
The JSON consists of an array of <code>&#91;context, book&#93;</code> where <code>context</code> is the serialized object <a href="https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/struct.PreprocessorContext.html"><code>PreprocessorContext</code></a> and <code>book</code> is a <a href="https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/book/struct.Book.html"><code>Book</code></a> object containing the content of the book.</p>
<p>The preprocessor should return the JSON format of the <a href="https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/book/struct.Book.html"><code>Book</code></a> object to stdout, with any modifications it wishes to perform.</p>
<p>The easiest way to get started is by creating your own implementation of the
<code>Preprocessor</code> trait (e.g. in <code>lib.rs</code>) and then creating a shell binary which
translates inputs to the correct <code>Preprocessor</code> method. For convenience, there
is <a href="https://github.com/rust-lang/mdBook/blob/master/examples/nop-preprocessor.rs">an example no-op preprocessor</a> in the <code>examples/</code> directory which can easily
be adapted for other preprocessors.</p>
<details>
<summary>Example no-op preprocessor</summary>
<pre class="playground"><code class="language-rust edition2018">// nop-preprocessors.rs&#10;&#10;//! A basic example of a preprocessor that does nothing.&#10;&#10;use crate::nop_lib::Nop;&#10;use clap::{Arg, ArgMatches, Command};&#10;use mdbook_preprocessor::book::Book;&#10;use mdbook_preprocessor::errors::Result;&#10;use mdbook_preprocessor::{Preprocessor, PreprocessorContext};&#10;use semver::{Version, VersionReq};&#10;use std::io;&#10;use std::process;&#10;&#10;fn make_app() -> Command {&#10;    Command::new("nop-preprocessor")&#10;        .about("A mdbook preprocessor which does precisely nothing")&#10;        .subcommand(&#10;            Command::new("supports")&#10;                .arg(Arg::new("renderer").required(true))&#10;                .about("Check whether a renderer is supported by this preprocessor"),&#10;        )&#10;}&#10;&#10;fn main() {&#10;    let matches = make_app().get_matches();&#10;&#10;    // Users will want to construct their own preprocessor here&#10;    let preprocessor = Nop::new();&#10;&#10;    if let Some(sub_args) = matches.subcommand_matches("supports") {&#10;        handle_supports(&#x26;preprocessor, sub_args);&#10;    } else if let Err(e) = handle_preprocessing(&#x26;preprocessor) {&#10;        eprintln!("{e:?}");&#10;        process::exit(1);&#10;    }&#10;}&#10;&#10;fn handle_preprocessing(pre: &#x26;dyn Preprocessor) -> Result&#x3C;()> {&#10;    let (ctx, book) = mdbook_preprocessor::parse_input(io::stdin())?;&#10;&#10;    let book_version = Version::parse(&#x26;ctx.mdbook_version)?;&#10;    let version_req = VersionReq::parse(mdbook_preprocessor::MDBOOK_VERSION)?;&#10;&#10;    if !version_req.matches(&#x26;book_version) {&#10;        eprintln!(&#10;            "Warning: The {} plugin was built against version {} of mdbook, \&#10;             but we're being called from version {}",&#10;            pre.name(),&#10;            mdbook_preprocessor::MDBOOK_VERSION,&#10;            ctx.mdbook_version&#10;        );&#10;    }&#10;&#10;    let processed_book = pre.run(&#x26;ctx, book)?;&#10;    serde_json::to_writer(io::stdout(), &#x26;processed_book)?;&#10;&#10;    Ok(())&#10;}&#10;&#10;fn handle_supports(pre: &#x26;dyn Preprocessor, sub_args: &#x26;ArgMatches) -> ! {&#10;    let renderer = sub_args&#10;        .get_one::&#x3C;String>("renderer")&#10;        .expect("Required argument");&#10;    let supported = pre.supports_renderer(renderer).unwrap();&#10;&#10;    // Signal whether the renderer is supported by exiting with 1 or 0.&#10;    if supported {&#10;        process::exit(0);&#10;    } else {&#10;        process::exit(1);&#10;    }&#10;}&#10;&#10;/// The actual implementation of the `Nop` preprocessor. This would usually go&#10;/// in your main `lib.rs` file.&#10;#&#91;allow(unreachable_pub, reason = "wouldn't be a problem in a proper lib.rs")&#93;&#10;mod nop_lib {&#10;    use super::*;&#10;&#10;    /// A no-op preprocessor.&#10;    pub struct Nop;&#10;&#10;    impl Nop {&#10;        pub fn new() -> Nop {&#10;            Nop&#10;        }&#10;    }&#10;&#10;    impl Preprocessor for Nop {&#10;        fn name(&#x26;self) -> &#x26;str {&#10;            "nop-preprocessor"&#10;        }&#10;&#10;        fn run(&#x26;self, ctx: &#x26;PreprocessorContext, book: Book) -> Result&#x3C;Book> {&#10;            // In testing we want to tell the preprocessor to blow up by setting a&#10;            // particular config value&#10;            match ctx&#10;                .config&#10;                .get::&#x3C;bool>("preprocessor.nop-preprocessor.blow-up")&#10;            {&#10;                Ok(Some(true)) => anyhow::bail!("Boom!!1!"),&#10;                Ok(_) => {}&#10;                Err(e) => anyhow::bail!("expect bool for blow-up: {e}"),&#10;            }&#10;&#10;            // we *are* a no-op preprocessor after all&#10;            Ok(book)&#10;        }&#10;&#10;        fn supports_renderer(&#x26;self, renderer: &#x26;str) -> Result&#x3C;bool> {&#10;            Ok(renderer != "not-supported")&#10;        }&#10;    }&#10;&#10;    #&#91;cfg(test)&#93;&#10;    mod test {&#10;        use super::*;&#10;&#10;        #&#91;test&#93;&#10;        fn nop_preprocessor_run() {&#10;            let input_json = r##"&#91;&#10;                {&#10;                    "root": "/path/to/book",&#10;                    "config": {&#10;                        "book": {&#10;                            "authors": &#91;"AUTHOR"&#93;,&#10;                            "language": "en",&#10;                            "src": "src",&#10;                            "title": "TITLE"&#10;                        },&#10;                        "preprocessor": {&#10;                            "nop": {}&#10;                        }&#10;                    },&#10;                    "renderer": "html",&#10;                    "mdbook_version": "0.4.21"&#10;                },&#10;                {&#10;                    "items": &#91;&#10;                        {&#10;                            "Chapter": {&#10;                                "name": "Chapter 1",&#10;                                "content": "# Chapter 1\n",&#10;                                "number": &#91;1&#93;,&#10;                                "sub_items": &#91;&#93;,&#10;                                "path": "chapter_1.md",&#10;                                "source_path": "chapter_1.md",&#10;                                "parent_names": &#91;&#93;&#10;                            }&#10;                        }&#10;                    &#93;&#10;                }&#10;            &#93;"##;&#10;            let input_json = input_json.as_bytes();&#10;&#10;            let (ctx, book) = mdbook_preprocessor::parse_input(input_json).unwrap();&#10;            let expected_book = book.clone();&#10;            let result = Nop::new().run(&#x26;ctx, book);&#10;            assert!(result.is_ok());&#10;&#10;            // The nop-preprocessor should not have made any changes to the book content.&#10;            let actual_book = result.unwrap();&#10;            assert_eq!(actual_book, expected_book);&#10;        }&#10;    }&#10;}</code></pre>
</details>
<h2 id="hints-for-implementing-a-preprocessor"><a class="header" href="#hints-for-implementing-a-preprocessor">Hints for implementing a preprocessor</a></h2>
<p>By pulling in <code>mdbook-preprocessor</code> as a library, preprocessors can have access to the
existing infrastructure for dealing with books.</p>
<p>For example, a custom preprocessor could use the
<a href="https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/fn.parse_input.html"><code>parse_input()</code></a> function to deserialize the JSON written to
<code>stdin</code>. Then each chapter of the <code>Book</code> can be mutated in-place via
<a href="https://docs.rs/mdbook-preprocessor/latest/mdbook_preprocessor/book/struct.Book.html#method.for_each_mut"><code>Book::for_each_mut()</code></a>, and then written to <code>stdout</code> with the <code>serde_json</code>
crate.</p>
<p>Chapters can be accessed either directly (by recursively iterating over
chapters) or via the <code>Book::for_each_mut()</code> convenience method.</p>
<p>The <code>chapter.content</code> is just a string which happens to be markdown. While it’s
entirely possible to use regular expressions or do a manual find &#x26; replace,
you’ll probably want to process the input into something more computer-friendly.
The <a href="https://docs.rs/mdbook-markdown/latest/mdbook_markdown/"><code>mdbook-markdown</code></a> crate exposes the <a href="https://crates.io/crates/pulldown-cmark"><code>pulldown-cmark</code></a> crate used by mdBook to parse Markdown. The <a href="https://crates.io/crates/pulldown-cmark-to-cmark"><code>pulldown-cmark-to-cmark</code></a> crate can be used to translate events back into markdown text.</p>
<p>The following code block shows how to remove all emphasis from markdown,
without accidentally breaking the document.</p>
<pre class="playground"><code class="language-rust edition2018"><span data-mdbook-hidden-line="true">//! This is a demonstration of an mdBook preprocessor which parses markdown&#10;</span><span data-mdbook-hidden-line="true">//! and removes any instances of emphasis.&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">use mdbook_preprocessor::book::{Book, Chapter};&#10;</span><span data-mdbook-hidden-line="true">use mdbook_preprocessor::errors::Result;&#10;</span><span data-mdbook-hidden-line="true">use mdbook_preprocessor::{Preprocessor, PreprocessorContext};&#10;</span><span data-mdbook-hidden-line="true">use pulldown_cmark::{Event, Parser, Tag, TagEnd};&#10;</span><span data-mdbook-hidden-line="true">use std::io;&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">fn main() {&#10;</span><span data-mdbook-hidden-line="true">    let mut args = std::env::args().skip(1);&#10;</span><span data-mdbook-hidden-line="true">    match args.next().as_deref() {&#10;</span><span data-mdbook-hidden-line="true">        Some("supports") => {&#10;</span><span data-mdbook-hidden-line="true">            // Supports all renderers.&#10;</span><span data-mdbook-hidden-line="true">            return;&#10;</span><span data-mdbook-hidden-line="true">        }&#10;</span><span data-mdbook-hidden-line="true">        Some(arg) => {&#10;</span><span data-mdbook-hidden-line="true">            eprintln!("unknown argument: {arg}");&#10;</span><span data-mdbook-hidden-line="true">            std::process::exit(1);&#10;</span><span data-mdbook-hidden-line="true">        }&#10;</span><span data-mdbook-hidden-line="true">        None => {}&#10;</span><span data-mdbook-hidden-line="true">    }&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">    if let Err(e) = handle_preprocessing() {&#10;</span><span data-mdbook-hidden-line="true">        eprintln!("{e}");&#10;</span><span data-mdbook-hidden-line="true">        std::process::exit(1);&#10;</span><span data-mdbook-hidden-line="true">    }&#10;</span><span data-mdbook-hidden-line="true">}&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">struct RemoveEmphasis;&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">impl Preprocessor for RemoveEmphasis {&#10;</span><span data-mdbook-hidden-line="true">    fn name(&#x26;self) -> &#x26;str {&#10;</span><span data-mdbook-hidden-line="true">        "remove-emphasis"&#10;</span><span data-mdbook-hidden-line="true">    }&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">    fn run(&#x26;self, _ctx: &#x26;PreprocessorContext, mut book: Book) -> Result&#x3C;Book> {&#10;</span><span data-mdbook-hidden-line="true">        let mut total = 0;&#10;</span><span data-mdbook-hidden-line="true">        book.for_each_chapter_mut(|ch| match remove_emphasis(&#x26;mut total, ch) {&#10;</span><span data-mdbook-hidden-line="true">            Ok(s) => ch.content = s,&#10;</span><span data-mdbook-hidden-line="true">            Err(e) => eprintln!("failed to process chapter: {e:?}"),&#10;</span><span data-mdbook-hidden-line="true">        });&#10;</span><span data-mdbook-hidden-line="true">        eprintln!("removed {total} emphasis");&#10;</span><span data-mdbook-hidden-line="true">        Ok(book)&#10;</span><span data-mdbook-hidden-line="true">    }&#10;</span><span data-mdbook-hidden-line="true">}&#10;</span><span data-mdbook-hidden-line="true">&#10;</span>fn remove_emphasis(num_removed_items: &#x26;mut usize, chapter: &#x26;mut Chapter) -> Result&#x3C;String> {&#10;    let mut buf = String::with_capacity(chapter.content.len());&#10;&#10;    let events = Parser::new(&#x26;chapter.content).filter(|e| match e {&#10;        Event::Start(Tag::Emphasis) | Event::Start(Tag::Strong) => {&#10;            *num_removed_items += 1;&#10;            false&#10;        }&#10;        Event::End(TagEnd::Emphasis) | Event::End(TagEnd::Strong) => false,&#10;        _ => true,&#10;    });&#10;&#10;    Ok(pulldown_cmark_to_cmark::cmark(events, &#x26;mut buf).map(|_| buf)?)&#10;}&#10;<span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">pub fn handle_preprocessing() -> Result&#x3C;()> {&#10;</span><span data-mdbook-hidden-line="true">    let pre = RemoveEmphasis;&#10;</span><span data-mdbook-hidden-line="true">    let (ctx, book) = mdbook_preprocessor::parse_input(io::stdin())?;&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">    let processed_book = pre.run(&#x26;ctx, book)?;&#10;</span><span data-mdbook-hidden-line="true">    serde_json::to_writer(io::stdout(), &#x26;processed_book)?;&#10;</span><span data-mdbook-hidden-line="true">&#10;</span><span data-mdbook-hidden-line="true">    Ok(())&#10;</span><span data-mdbook-hidden-line="true">}</span></code></pre>
<p>Take a look at the <a href="https://github.com/rust-lang/mdBook/tree/master/examples/remove-emphasis/">full example source</a> for more details.</p>
<h2 id="implementing-a-preprocessor-with-a-different-language"><a class="header" href="#implementing-a-preprocessor-with-a-different-language">Implementing a preprocessor with a different language</a></h2>
<p>The fact that mdBook utilizes stdin and stdout to communicate with the preprocessors makes it easy to implement them in a language other than Rust.
The following code shows how to implement a simple preprocessor in Python, which will modify the content of the first chapter.
The example below follows the configuration shown above with <code>preprocessor.foo.command</code> actually pointing to a Python script.</p>
<pre><code class="language-python">import json&#10;import sys&#10;&#10;&#10;if __name__ == '__main__':&#10;    if len(sys.argv) > 1: # we check if we received any argument&#10;        if sys.argv&#91;1&#93; == "supports": &#10;            # then we are good to return an exit status code of 0, since the other argument will just be the renderer's name&#10;            sys.exit(0)&#10;&#10;    # load both the context and the book representations from stdin&#10;    context, book = json.load(sys.stdin)&#10;    # and now, we can just modify the content of the first chapter&#10;    book&#91;'items'&#93;&#91;0&#93;&#91;'Chapter'&#93;&#91;'content'&#93; = '# Hello'&#10;    # we are done with the book's modification, we can just print it to stdout, &#10;    print(json.dumps(book))&#10;</code></pre>
</div>
