merman-rustdoc
Render Mermaid diagrams in rustdoc as inline SVG.
merman-rustdoc is a small proc-macro integration for crates that want diagrams in API docs
without loading Mermaid JavaScript in the browser. It reads Mermaid code fences and include_mmd!
lines from doc comments, renders them with Merman during cargo doc, and writes the resulting SVG
back into the generated rustdoc page.
Choose merman-rustdoc when you want Mermaid diagrams to be part of the generated rustdoc HTML
itself: no browser Mermaid runtime, no CDN dependency, offline-friendly docs, and CI-visible diagram
failures. If you only need the smallest possible macro and are comfortable with browser-side
Mermaid rendering, a JavaScript-based rustdoc integration may be a lighter fit.
Install
Use a normal dependency for the simplest setup:
[]
= "=0.7.0"
This works for local cargo doc and for docs.rs because the examples below use cfg_attr(doc, ...).
The macro only expands during rustdoc builds, but Cargo will still compile the dependency during
ordinary builds.
If you want ordinary builds to avoid compiling merman-rustdoc, make it optional behind a
documentation feature:
[]
= { = "=0.7.0", = true }
[]
= ["dep:merman-rustdoc"]
[]
= ["doc-diagrams"]
With this optional setup, build docs locally with:
docs.rs will also enable doc-diagrams because of the package.metadata.docs.rs section.
Quickstart
Put #[cfg_attr(doc, merman_rustdoc::merman)] on any item whose docs contain a Mermaid fence.
/// Documentation prose stays before the diagram.
///
/// ```mermaid
/// flowchart TD
/// Source[Mermaid source] --> Macro[merman-rustdoc]
/// Macro --> Svg[Inline SVG]
/// Svg --> Docs[Rustdoc page]
/// ```
///
/// Documentation prose stays after the diagram too.
When you run cargo doc, the Mermaid fence is replaced with an inline <svg> in the generated
HTML, preserving the prose around it. The source view still shows your original Rust source. If you
use the optional dependency setup above, run cargo doc --features doc-diagrams instead.
Rendered output:

A full rustdoc page keeps the item heading, signature, prose, and generated SVG together:

The default theme = "rustdoc" mode embeds light and dark SVG variants in the generated rustdoc
page and lets rustdoc's current theme choose which one is visible.
Common Patterns
Functions
/// Parse, layout, and render a diagram.
///
/// ```mermaid
/// flowchart LR
/// Parse --> Layout --> Svg[SVG]
/// ```
Modules
/// Rendering pipeline.
///
/// ```mermaid
/// flowchart TD
/// Core[merman-core] --> Render[merman-render]
/// Render --> Rustdoc[merman-rustdoc]
/// ```
Structs
/// A renderer configured for rustdoc output.
///
/// ```mermaid
/// flowchart TD
/// Config --> Renderer
/// Renderer --> InlineSvg[Inline SVG]
/// ```
;
Traits
/// Something that can render a diagram.
///
/// ```mermaid
/// sequenceDiagram
/// participant Caller
/// participant Renderer
/// Caller->>Renderer: render(source)
/// Renderer-->>Caller: svg
/// ```
Impl Blocks
;
/// High-level client workflow.
///
/// ```mermaid
/// flowchart TD
/// New[new()] --> Render[render()]
/// Render --> Done[SVG]
/// ```
Include Mermaid Files
Large diagrams are easier to maintain in separate .mmd files.
my-crate/
├── Cargo.toml
├── src/lib.rs
└── docs/architecture.mmd
docs/architecture.mmd:
flowchart TD
Api[Public API] --> Core[Core Model]
Core --> Render[Renderer]
Render --> Docs[Rustdoc SVG]
src/lib.rs:
/// Crate architecture.
///
/// include_mmd!("docs/architecture.mmd")
Include paths are resolved relative to the consuming crate's CARGO_MANIFEST_DIR, not relative to
the source file.
Multiple Diagrams
You can put more than one diagram on the same item. SVG ids are scoped per diagram so inline SVG definitions do not collide.
/// Input flow:
///
/// ```mermaid
/// flowchart LR
/// Source --> Parse --> Model
/// ```
///
/// Output flow:
///
/// ```mermaid
/// flowchart LR
/// Model --> Layout --> Svg[SVG]
/// ```
Backtick and tilde fences are both supported:
/// ~~~ mermaid
/// flowchart TD
/// A --> B
/// ~~~
Options
The attribute accepts string options:
/// ```mermaid
/// flowchart TD
/// A --> B
/// ```
| Option | Values | Default | Meaning |
|---|---|---|---|
scope |
item, tree |
item |
Controls whether only the annotated item or the inline item tree is rewritten. |
pipeline |
readable, parity, resvg-safe |
readable |
Selects the SVG output pipeline. |
fail |
error, keep-source |
error |
Controls what happens when rendering or file includes fail. |
source |
hide, details |
hide |
Adds a collapsed Mermaid source block under the SVG when set to details. |
sanitize |
strict, off |
strict |
Checks rendered SVG for script elements, event attributes, and unsafe resource references. |
theme |
rustdoc, mermaid, or a supported Mermaid theme name |
rustdoc |
Controls whether diagrams follow rustdoc light/dark themes, use Mermaid source config, or use a fixed Mermaid theme. |
scope = "tree"
Use scope = "tree" when one attribute should process docs inside an inline item tree. This is most
useful for modules, but it also handles docs on impl methods, trait methods, fields, and enum
variants that are visible in the annotated item.
scope = "tree" requires inline Rust syntax. It does not inspect external module files:
That form fails with a clear error because a proc macro cannot safely recurse into external.rs.
source = "details"
Use this when readers should be able to inspect the Mermaid source from the generated docs.
/// ```mermaid
/// flowchart TD
/// User --> Api --> Database
/// ```
The generated page will show the SVG first, then a collapsed "Mermaid source" block.
fail = "keep-source"
Use this for documentation builds where a broken diagram should not fail the whole crate. The
original Mermaid fence or include_mmd! line is left in place when rendering fails.
/// ```mermaid
/// flowchart TD
/// A --> B
/// ```
The default is fail = "error", which is better for CI and release builds because diagram problems
are caught early.
pipeline
Choose the SVG pipeline that fits the target:
readable: default. Produces stable, readable SVG for rustdoc.parity: closer to Merman's Mermaid-parity SVG path.resvg-safe: post-processes SVG for raster-oriented consumers.
/// ```mermaid
/// flowchart TD
/// A --> B
/// ```
sanitize = "strict"
sanitize = "strict" is the default. It validates rendered SVG before inserting it into rustdoc and
fails the documentation build if it finds script elements, event attributes, javascript: URLs, or
remote resource references such as <image href="https://...">.
/// ```mermaid
/// flowchart TD
/// A --> B
/// ```
Use sanitize = "off" only when you are deliberately inspecting raw renderer output:
/// ```mermaid
/// flowchart TD
/// A --> B
/// ```
theme
theme = "rustdoc" is the default. It renders light and dark SVG variants during cargo doc, then
uses rustdoc's page theme state to show the matching variant. Readers can switch rustdoc light,
dark, or ayu themes without loading Mermaid JavaScript in the browser.
The switch is CSS-only: both SVG variants are embedded in the rustdoc HTML, and a small inline style
uses rustdoc's :root[data-theme] attribute to show the light or dark variant. The browser never
loads a Mermaid runtime to render or recolor diagrams.
Use theme = "mermaid" when you want a single SVG and want Mermaid source-level config, such as
front matter or an %%init%% directive, to decide the theme:
/// ```mermaid
/// %%{init: {"theme": "base"}}%%
/// flowchart TD
/// A --> B
/// ```
Use a fixed Mermaid theme when you want one static SVG regardless of the reader's rustdoc theme:
/// ```mermaid
/// flowchart TD
/// A --> B
/// ```
Supported fixed theme names follow Merman's Mermaid theme surface: default, base, dark,
forest, neutral, neo, neo-dark, redux, redux-dark, redux-color, and
redux-dark-color.
Rustdoc theme mode and fixed themes are passed as Mermaid site config. Source-level config still
wins, so a diagram can override them with front matter or an %%init%% directive:
%%{init: {"theme": "base"}}%%
flowchart TD
A --> B
When source-level config sets an explicit Mermaid theme, both rustdoc variants may render with that source-selected theme. Use source-level theme directives only when a diagram should intentionally opt out of rustdoc theme adaptation.
Re-exports
Inline SVG is stored in the expanded rustdoc attributes. That makes re-exported pages work when the
upstream item was documented with merman-rustdoc.
// upstream crate
/// ```mermaid
/// flowchart TD
/// Upstream --> Reexport
/// ```
;
// downstream crate
pub use DiagrammedType;
If the upstream crate uses the optional documentation feature setup, that feature still has to be enabled when its docs are built. A downstream re-export cannot render diagrams that were never expanded upstream.
What Gets Rendered
Supported today:
- Mermaid fences using backticks or tildes.
include_mmd!("path/to/file.mmd")lines outside other Markdown code fences.- Item docs on functions, modules, structs, traits, and impl blocks.
- Recursive inline item docs with
scope = "tree". - Multiple diagrams on the same item.
- Footnotes and normal Markdown around diagrams.
- Re-exported item docs when the upstream item was rendered first.
Not supported today:
- Crate-level inner docs using
//!. - Recursive processing for external
mod name;files. - Running Mermaid JavaScript in the browser.
- Fetching Mermaid source or assets from remote URLs.
- Copying external SVG files into the rustdoc output directory.
Troubleshooting
The generated docs still show a Mermaid code block
Make sure the item has the attribute:
If you use the optional dependency setup, also make sure the documentation feature is enabled:
Also gate the attribute with the same feature:
include_mmd! cannot find a file
Paths are relative to CARGO_MANIFEST_DIR.
/// include_mmd!("docs/architecture.mmd")
For a crate at my-crate/Cargo.toml, that resolves to:
my-crate/docs/architecture.mmd
docs.rs does not render diagrams
The normal dependency setup does not need docs.rs metadata. If merman-rustdoc is optional behind a
documentation feature, add the docs.rs feature configuration:
[]
= ["doc-diagrams"]
A diagram failure blocks cargo doc
That is the default behavior. Use fail = "keep-source" if you prefer documentation builds to keep
going while preserving the original Mermaid source.
scope = "tree" fails on mod name;
Use an inline module when you want recursive processing:
External module files are not traversed by the proc macro.
Can I use this on crate-level //! docs?
No. merman-rustdoc rewrites item-level outer docs. It does not rewrite crate-level inner docs
written with //!.
Put crate-level diagrams on a public module or item instead:
/// Crate architecture.
///
/// ```mermaid
/// flowchart TD
/// Crate --> Module
/// ```
Does this rewrite #[doc = include_str!(...)]?
No. merman-rustdoc rewrites literal rustdoc lines that come from item doc comments and
include_mmd!("path.mmd") lines. It does not evaluate or rewrite Markdown loaded through
#[doc = include_str!("...")].
Use include_mmd! for Mermaid files:
/// include_mmd!("docs/architecture.mmd")
Do rustdoc symbol links work inside Mermaid diagrams?
No. Mermaid source is rendered to SVG before rustdoc resolves intra-doc links. Text inside the SVG
does not participate in rustdoc link resolution, so labels such as [Type](crate::Type) are treated
as Mermaid text or Mermaid links, not rustdoc symbol links.
Normal Mermaid links follow whatever Merman renders, subject to sanitize = "strict".
What about themes?
By default, merman-rustdoc follows rustdoc's light/dark theme setting. It renders light and dark
SVG variants during cargo doc and uses rustdoc's data-theme state to show the matching variant.
The switch is CSS-only: both variants are embedded in the generated HTML, and the browser does not
load Mermaid JavaScript to render or recolor diagrams.
Use theme = "mermaid" for a single SVG controlled by Mermaid source config. Use theme = "dark"
or another supported Mermaid theme to choose one fixed build-time theme. If your Mermaid source uses
Mermaid's own source-level config, such as an %%init%% directive, it is passed to Merman with the
rest of the diagram and overrides the rustdoc-level theme default:
%%{init: {"theme": "base"}}%%
flowchart TD
A --> B
Whether a specific Mermaid theme directive works depends on Merman's renderer support for that
diagram and config. merman-rustdoc does not add a separate SVG recoloring layer on top.
Why Build-Time SVG
Many rustdoc Mermaid integrations inject Mermaid JavaScript into the generated page. That works, but it makes rendering depend on browser execution, script loading, and sometimes remote assets.
merman-rustdoc renders before the page is opened:
- no Mermaid JavaScript is injected;
- no CDN is required;
- docs work offline after they are generated;
- SVG is present in the HTML that rustdoc writes;
- broken diagrams can fail CI before release.
Acknowledgements
Thanks to aquamarine for proving that Mermaid diagrams
inside rustdoc comments are useful and ergonomic. merman-rustdoc follows the same user-facing idea,
but renders SVG with Merman during documentation builds instead of loading Mermaid in the browser.