merman: rendering Mermaid diagrams from Rust, with no JavaScript runtime
Mermaid.js, but headless, in Rust.
At a glance
- What is it?
- Merman is a headless Rust implementation of Mermaid covering parsing, layout and rendering, so a diagram can be produced inside a Rust program, a CI job or a documentation build without starting Node.js, Puppeteer or Chromium. It ships typed output targets, an ASCII renderer, a CLI, a language server, WebAssembly and native bindings, and it is the Rust Mermaid backend behind Zed.
- Who is it for?
- merman suits Rust developers who need Mermaid diagrams in a native application, a CI pipeline or a documentation build, and teams that want to remove a headless browser from their build graph. It does not suit you if you need Mermaid features the Rust port has not implemented, since compatibility is claimed per diagram family rather than wholesale, and it does not suit a team that will not accept an alpha authoring format, which is what the custom theme recipe is today.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Mermaid without a browser in the middle
Merman describes itself as a headless Rust implementation of Mermaid for parsing, layout and rendering, and the project description is shorter still: Mermaid.js, but headless, in Rust.
The problem it solves is the dependency graph. The usual way to render a Mermaid diagram outside a browser is to run the JavaScript implementation under Node.js with Puppeteer driving a headless Chromium. That works, and it drags a browser into your build. Merman removes that: it turns Mermaid source into diagrams without starting Node.js, Puppeteer, Chromium or another JavaScript runtime in the native rendering path.
The README lists where that matters, and they are the environments where a browser is most unwelcome: native applications, CI jobs and documentation builds. A documentation generator that runs on a build server does not want to download a browser to typeset a diagram. A desktop application does not want to ship one either.
The same README also says the three ways you can use it, and they are a useful summary of the project's shape: embed it in a Rust application, render from the command line, or use the browser and native bindings. The browser binding is the interesting one, because it means the same engine runs in a web page and in a server process.
The licence is MIT or Apache-2.0, at your option, and the project version is 0.9.1.
The most concrete evidence of quality in the README is not a benchmark but an adoption. The README notes in a callout that Zed uses Merman as its Rust Mermaid backend, and links the merged integration pull request. A native editor shipping it is a stronger argument than any claim about parity.
Thirty crates, one engine
The workspace Cargo.toml lists around thirty members, and the naming shows the structure of the project.
There is a family of core crates: merman-core, merman-render, merman-layout-elk and merman-elk-layered for the layout engine, merman-ascii for the text renderer, merman-export for the raster and document outputs, and merman-analysis and merman-editor-core for the pieces an editor needs. Then there is merman, the facade crate you depend on, and merman-doc and merman-rustdoc for documentation tooling.
The binding and integration layer is where the breadth shows. merman-cli is the command line tool, merman-lsp is a language server, merman-wasm is the WebAssembly build, merman-ffi and merman-uniffi are the native binding layers, merman-android-jni is the Android bridge, merman-typst-plugin is a Typst plugin, and merman-bindings-core is the shared binding support. There is also a merman-theme-contract crate plus merman-theme-authoring-fixtures and merman-theme-acceptance, which is the shape of a project that treats its theme format as a published contract with tests around it.
Then there are the helpers with their own names: dugong and dugong-graphlib for graph algorithms, manatee, roughr, and xtask for task automation. A distribution/tree-sitter-mermaid member points at the grammar work.
The workspace metadata is simple and conventional: version 0.9.1, edition 2024, rust-version 1.95, one author, and a release metadata block listing roughr-merman and tree-sitter-mermaid as independent packages. That last line matters, because it means the grammar and one companion crate are versioned on their own schedule rather than in lockstep with the main crate.
Thirty crates is a lot of surface, and it is the clearest sign that this is no longer a single-purpose library but an ecosystem project.
One-shot rendering, and a model for repeated rendering
The README spends a section on the difference between rendering once and rendering many times, and the type table is where the design becomes clear.
A Renderer holds the engine and shared defaults, and lives for one call or many. A RenderRequest describes one independent operation, borrowing the source and selecting a target, overrides and control. OperationControl carries cooperative cancellation and an optional deadline, and belongs to one operation, to be cloned for the cancelling task or thread. SemanticArtifact pairs the parsed render model with its operation context, and PreparedSource, in the current source branch, shares frozen parse facts across independently controlled outputs.
The quick start is the one-shot form: create a renderer, issue one request, drop it.
fn main() -> Result<(), Box<dyn std::error::Error>> {
let output = Renderer::new().render(RenderRequest::svg(
"flowchart TD\n A[Start] --> B[Done]",
OperationControl::new(),
SvgRequest::default(),
))?;
let RenderOutput::Svg(Some(svg)) = output else {
return Err("no Mermaid diagram detected".into());
};
print!("{}", svg.svg());
Ok(())
}For a batch or a service, the guidance is to keep one configured Renderer when the inputs share parse options, runtime policy, site config or resource limits. The important sentence follows: it shares settings, not parsed state. Every call parses its own source and carries a fresh RenderRequest and, normally, a fresh OperationControl.
So a reused renderer is not a cache. For redraws of unchanged source, the current source branch adds Renderer::prepare_source() and render_prepared(), which reuse canonical semantics while each output gets fresh controls, budgets, runtime state and layout, and a parse-affecting change forces a new preparation.
There is a related warning for editors. If several SVGs share one DOM, each request needs a diagram_id that stays unique after merman::svg::sanitize_svg_id() normalization, and an editor that wants to keep analysis between queries should use a host-owned snapshot from merman::editor, because a reused renderer is not an editor session.
Typed output targets behind Cargo features
The output target table is the API surface in miniature, and it shows a project that is careful about what a request can produce.
To parse a typed Mermaid model you use Engine with ParseOptions, gated behind the required diagram-* selectors or all-diagrams. To prepare or inspect the semantic artifact you use Renderer::prepare_semantic() or RenderTarget::Semantic. To render Mermaid-style SVG you use RenderRequest::svg(), behind the svg feature. To complete one document for several graphical targets you use RenderRequest::document(), behind svg plus each output feature used. To inspect layout JSON or an SVG capability plan you use RenderRequest::layout_json() or RenderRequest::svg_plan(). To render terminal text for supported families you use RenderRequest::ascii(), behind the ascii feature. And to export one-shot PNG, JPEG or PDF you use RenderRequest::png(), jpeg() or pdf().
Two design points stand out.
The first is that diagram families are a compile-time choice rather than a runtime one. The Cargo feature selects which diagram types are compiled in, so a program that only draws flowcharts does not carry the code for every Mermaid diagram type. That is a real win in binary size and in build time, and it is also a constraint: adding a diagram family later means changing the feature set and recompiling.
The second is the difference between svg and document. A document request completes one diagram for several graphical targets at once, which is what an editor that shows a preview and an export side by side needs, and it is not the same as rendering the same source twice independently.
The layout engine also deserves a mention, because it is where a diagram renderer is won or lost. The crates merman-layout-elk and merman-elk-layered show that the layout side is built on the ELK algorithm, layered, with its own crate and its own tests.
Compatibility is checked against pinned upstream fixtures
The claim Merman makes is not that it reimplements Mermaid perfectly, but that compatibility is verified. The feature list says parsing, layout, configuration, theming and SVG structure are checked against pinned upstream source and fixtures, and the badges point at a CI workflow, crates.io and the Rust API documentation.
That distinction matters, because a Mermaid renderer that is subtly different is worse than one that is visibly different. If the fixture suite compares SVG structure and configuration handling against a pinned upstream version, then a change in Mermaid that Merman has not caught shows up as a test failure rather than as a diagram that looks a bit off in a documentation site. The cost of that guarantee is that Merman has to be re-verified whenever upstream moves, which is a permanent tax rather than a one-off.
The repository layout shows the machinery behind that claim. There is a fixtures/ directory, a fuzz/ directory for fuzzing, deny.toml for dependency policy checks, a THIRD_PARTY_LICENSES/ directory with a THIRD_PARTY_NOTICES.md, a capabilities/ directory and a contracts/ directory, which is a vocabulary that suggests the project's public promises are written down as testable contracts. There is also a Nix layer, flake.nix, flake.lock, default.nix and a nix/ directory, plus a rust-toolchain.toml pinning the toolchain, and scripts/ and tools/ directories.
For an adopter, the practical question is whether the fixture comparison covers the diagram families and the configuration surface you use. The README does not publish a per-feature support matrix, so that check has to happen by rendering your own diagrams and looking at the result.
Themes with a versioned recipe, and an authoring format that is alpha
The theming story is the most developed part of the project and the one with the clearest caveat.
The current source branch includes compiled visual presets such as Brutalist, Spotless and Cyberpunk, selectable from the Playground's Theme menu. The Rust example is:
cargo run --locked -p merman --features svg --example theme_preset > themed.svgThe custom theme route goes further. You can use the Custom theme option to import, edit, validate and download a versioned theme recipe, and workspace share links preserve the applied custom recipe. The README stresses that the same JSON works through the Rust, Web, Node and native CLI APIs, so a theme is not locked to one surface.
The caveat is explicit. The authoring format is alpha, and the documentation page on creating, customizing and sharing diagram themes is where to look for examples, configuration precedence and export limitations. Alpha for an authoring format means the recipe you save today may need to be re-exported after a format change, so themes are worth treating as versioned assets rather than as permanent files.
The Playground is the other piece of the theme story, and it has a property worth knowing: its hosted build follows its deployed package version. So the Playground shows you the behaviour of the current release rather than of the main branch, which is the right choice for a tool where output fidelity matters.
A rapid release line and two separately versioned packages
The release history is fast. 0.9.0 was published on 2026-10-12, tree-sitter-mermaid-v0.3.1 on the same day, and 0.9.1 on 2026-10-18. The last push to the repository was on 2026-10-20.
A minor version every couple of weeks, with a patch two days later, is the rhythm of a project that is still finding its API. It also means the pin in your Cargo.toml matters, because a minor bump can change how a diagram renders.
The workspace release metadata names the two packages that version independently, roughr-merman and tree-sitter-mermaid. The second one is the grammar, and it is the piece an editor integration depends on. Keeping it on its own cycle is the right call, since a grammar change and an engine change have nothing to do with each other, but it also means an integration has two versions to keep compatible rather than one.
The rest of the tree reinforces that this is a project with a long tail of surfaces. There is a playground/ directory, platforms/ for the web and other platform packages, distribution/ for the packaged artifacts, docs/ for the documentation including a migration guide for host integration, a CHANGELOG.md, a CONTEXT.md, an about.toml, scripts/ and tools/, a .agents/ directory and an AGENTS.md.
The honest summary is that merman is a serious project rather than a weekend port. It has a compatibility testing strategy, an explicitly versioned contract surface, a grammar that ships separately, and a consumer in a shipping editor. Its costs are the ones you would expect from that: a fast release cadence to track, a feature-gated API you have to learn, and a theme format that is still alpha.
Editorial conclusion
merman suits Rust developers who need Mermaid diagrams in a native application, a CI pipeline or a documentation build, and teams that want to remove a headless browser from their build graph. It does not suit you if you need Mermaid features the Rust port has not implemented, since compatibility is claimed per diagram family rather than wholesale, and it does not suit a team that will not accept an alpha authoring format, which is what the custom theme recipe is today. Check three things before you commit: which diagram families you actually use, since the output targets are gated behind per-family Cargo features, whether the SVG output matches what the upstream Mermaid renderer produces for your diagrams, and whether the version of merman you pin matches the tree-sitter grammar release you use for editing. The last push to the repository was on 2026-10-20 and the newest release is 0.9.1 from 2026-10-18.
Frequently asked questions
What is merman and what problem does it solve?
It is a headless Rust implementation of Mermaid for parsing, layout and rendering. It produces diagrams without starting Node.js, Puppeteer, Chromium or another JavaScript runtime in the native rendering path, which makes it usable in Rust applications, CI jobs and documentation builds.
How do I render a Mermaid diagram with merman?
Add merman = "0.9.1" to your Cargo.toml with Rust 1.95 or later, then call Renderer::new().render(RenderRequest::svg(...)) and print the resulting SVG. From a source checkout the maintained example is cargo run --locked -p merman --example render_svg > diagram.svg.
What output formats does merman support?
Typed Mermaid models through Engine and ParseOptions, a semantic artifact through Renderer::prepare_semantic() or RenderTarget::Semantic, SVG through RenderRequest::svg(), layout JSON and an SVG capability plan, ASCII text for supported families through RenderRequest::ascii(), and one-shot PNG, JPEG or PDF through the export methods. Each target is gated behind a Cargo feature.
Can I reuse a parsed diagram across several renderings?
The current source branch exposes Renderer::prepare_source() and render_prepared() to reuse canonical parse facts while each output gets fresh controls, budgets, runtime state and layout, and a parse-affecting change requires a new preparation. A reused Renderer itself shares settings, not parsed state.
Is merman used in production anywhere?
The README states that Zed uses Merman as its Rust Mermaid backend, and links the merged integration pull request. The project also publishes a Playground, a CLI, a language server, WebAssembly and native bindings.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/latias94-merman)Was this useful?