Skip to main content

Python-native Mermaid diagram converter – no browser, Node.js, or npm required. Renders diagrams to SVG/PNG/PDF using an embedded QuickJS engine and resvg, perfect for documentation automation and CI/CD pipelines.

Project description

mermaidx — Mermaid Diagram Converter for Python

PyPI Python License: MIT Tests GitHub stars

Convert Mermaid diagrams to SVG, PNG, PDF, or ASCII art — fully offline and fast, just pip install mermaidx.

Completely browserless. No Node.js, no npm, no Chrome, no system packages, nothing to compile. And because there's no browser to boot, mermaidx renders noticeably faster than the official mermaid-cli, which drives a real headless Chrome through Puppeteer for every single diagram — mermaidx uses a fast, embedded JS engine instead.

pip install mermaidx

That's it — SVG, PNG, PDF, and ASCII output all work out of the box; nothing else to install.

Which install do you need?

  • pip install mermaidx — this alone is enough for everything above. It gives you the default backend (backend="quickjs", the embedded JS engine this README is about) and nothing else to think about.
  • pip install mermaidx[v8] — swaps the embedded JS engine from QuickJS-ng to real V8 (via mini-racer) when available, since V8's JIT renders the same real mermaid.js noticeably faster (2-4.5x in our own benchmarks) — same output, same API, still zero system dependencies. Falls back to QuickJS-ng automatically if mini-racer isn't installed, and for the one diagram type V8 can't handle (mindmap, see Additional backends) even when it is.
  • pip install mermaidx[rust] — adds the optional Rust backend (mmdr) for extra speed on top of that, selectable with backend="rust".
  • pip install mermaidx[all] — the easy option: every optional backend, in one command.

None of these need a system dependency, a system Mermaid/Node install, or even a compiler — including [v8]/[rust]/[all], which install prebuilt wheels, not source you build locally.

There isn't really an equivalent to this in the Python ecosystem. Every other Mermaid-to-image tool reachable from Python — including the official mermaid-cli itself — works by driving an actual browser (Puppeteer/Chrome) or shelling out to a separate Node.js process. mermaidx is the only one that renders real, current Mermaid JS with no browser and no subprocess at all.

Looking for mmdc? That was this project's old name, renamed to mermaidx — see History for why. mmdc is no longer published or maintained under that name; install mermaidx instead.


Why mermaidx?

The official Mermaid CLI (@mermaid-js/mermaid-cli) works by spinning up a real headless Chrome via Puppeteer for every render. That works, but a full browser is slow to start and heavy to install (~170MB+ of Chromium), which shows up directly in wall-clock time — especially in CI pipelines rendering many diagrams, or anything short-lived like a serverless function.

mermaidx renders the actual, current Mermaid v11 JS library — not a reimplementation, not a subset — but runs it inside a small embedded JavaScript engine instead of a browser. No browser process to spawn, no page to load, no DOM to boot — just the JS engine running Mermaid's own layout code directly. That's the whole speed difference in one sentence: browser vs. no browser.


Quick Start

Open in Colab

import mermaidx

d = mermaidx.render("""
graph TD
    A[Install] --> B[Import]
    B --> C[Convert]
    C --> D[Done]
""")

d.save("diagram.svg")
d.save("diagram.png", scale=2.0)
d.save("diagram.pdf", pdf_format="A4")
print(d.ascii())
mermaidx -i diagram.mermaid -o diagram.svg
mermaidx -i diagram.mermaid -o diagram.png --scale 2.0
cat diagram.mermaid | mermaidx -i - -o diagram.pdf

Jupyter

A Diagram displays automatically as the last expression in a cell — no extra code needed, the same way a DataFrame or a matplotlib figure does:

import mermaidx
mermaidx.render("flowchart LR; A-->B-->C")   # just shows up

.show() displays it explicitly (e.g. inside a loop) — it always shows exactly what .svg() returns, so what you see is this package's own render output, not a separate re-render through some other engine.

See examples/jupyter_demo.ipynb for a full walkthrough: display, PNG/PDF/ASCII export, themes, and batch rendering.


How It Works

flowchart LR
    A[Mermaid source] --> B["QuickJS-ng (or V8, optional)"]
    B -->|"mermaid.js v11 (bundled)"| C[SVG]
    C --> D[resvg]
    D --> E[PNG]
    C --> F["hand-written PDF writer<br/>(stdlib only)"]
    F --> G[PDF]

    H[bundled DejaVu Sans] -.font metrics.-> B
    H -.same font, forced.-> D

Everything happens in one process, no subprocess, no I/O — with one deliberate exception, backend="v8", noted below.

  • SVG — mermaid.js runs inside QuickJS-ng (default, in-process) or, optionally, real V8 (backend="v8", in its own child process — see JS engine: QuickJS vs V8 for why) against a minimal fake DOM/SVG implementation. The one thing a fake DOM can't fabricate — real text metrics (getBBox/getComputedTextLength) — is bridged back into Python (QuickJS) or reproduced exactly from a precomputed per-glyph advance-width table (V8), both reading the same bundled font.
  • PNG — the SVG is rasterized by resvg, forced to use that same bundled font, so what mermaid measured during layout is exactly what gets painted.
  • PDF — a small hand-written PDF writer (stdlib zlib/struct only) embeds the rendered pixels directly. No Pillow, no Cairo, no reportlab — every mainstream "put an image in a PDF" library pulls in Pillow as a transitive dependency; this avoids that entirely.
  • ASCII — a completely separate, lightweight path via termaid (pure Python, ~700KB, zero dependencies), which parses the Mermaid source itself rather than going through the SVG.

Rendering is CPU-bound, synchronous — there's no browser to wait on, so there's nothing for async to usefully overlap. See mermaidx.render_many() below for real parallelism instead.

Every backend is a small subclass of one shared DiagramBaseDiagram for 'quickjs'/'v8', DiagramRust for anything from the optional mmdr package. Subclasses only override the private _svg() hook; the public, cached svg()/png()/pdf()/raw()/numpy()/ascii()/save() are all written once in the base class and work identically regardless of which backend produced the SVG.


Python API

render(source, backend=None, **opts) -> Diagram

import mermaidx

d = mermaidx.render("flowchart LR; A-->B-->C")

render() itself does nothing but store the source — every Diagram method below is lazy and cached: nothing is computed until you call it, and calling it again with the same arguments returns the memoized result instead of recomputing.

Method Returns Notes
.svg() str Computed on first call, cached after
.png(width?, height?, scale?, background?) bytes Aspect ratio always preserved
.pdf(pdf_format?, pdf_landscape?, pdf_margin?, width?, height?, scale?, background?) bytes pdf_format=None (default) fits the page to the diagram
.ascii(**opts) str Renders straight from the Mermaid source, doesn't need .svg() first
.raw(width?, height?, background?) (bytes, w, h) Raw RGBA8888, no imaging library involved
.numpy(width?, height?, background?) np.ndarray (H, W, 4) uint8; requires numpy
.save(path, format=None, ...) None Format from format=, or inferred from the extension otherwise
._repr_svg_() str Automatic inline rendering in Jupyter/IPython
d.svg() is d.svg()      # True -- second call is a cache hit, not a re-render
d.png(width=1200, background="#ffffff")
d.raw()                 # (bytes, width, height) -- RGBA8888
d.numpy()                # np.ndarray, no Pillow needed
d.ascii()                # ASCII/Unicode box-drawing art
d.save("out.pdf", pdf_format="A4", pdf_margin="1cm")

save(): format from the extension, or forced explicitly

d.save("out.svg")                       # -> svg
d.save("out.png")                       # -> png
d.save("out.pdf")                       # -> pdf
d.save("out.txt")                       # -> ascii
d.save("out.whatever", format="png")    # force a format regardless of extension

Themes, config, CSS

mermaidx.render(source, theme="dark")                    # "default" | "forest" | "dark" | "neutral"
mermaidx.render(source, config={"flowchart": {"curve": "basis"}})
mermaidx.render(source, css=".node rect { rx: 8; ry: 8; }")

Parallel batch rendering

Rendering is pure CPU work — no I/O to overlap, so real concurrency means real processes, not async:

diagrams = mermaidx.render_many(sources, workers=4, theme="dark")
for d, name in zip(diagrams, output_names):
    d.save(name)

Each worker process starts its own persistent engine once and reuses it for every diagram routed to it.

ASCII / terminal output

Works out of the box — termaid (pure Python, ~700KB, zero dependencies of its own) is a core dependency, not an optional extra:

print(mermaidx.render_ascii("graph LR; A-->B-->C"))
# or, equivalently: mermaidx.render("graph LR; A-->B-->C").ascii()
┌───┐    ┌───┐    ┌───┐
│ A ├───►│ B ├───►│ C │
└───┘    └───┘    └───┘

Low-level utilities

Rasterize any SVG string directly, without going through render():

from mermaidx import svg_to_png, svg_to_raw

svg = open("diagram.svg").read()
png = svg_to_png(svg, width=1200, background="#ffffff")
raw, w, h = svg_to_raw(svg)

JS engine: QuickJS vs V8 (optional)

pip install mermaidx[v8]   # adds mini-racer (real V8) as a selectable JS engine

Both backends run the exact same real mermaid.js and produce byte-for-byte identical SVG output — the only difference is which JS engine runs it:

mermaidx.render(source)                    # backend="quickjs" (default) — always available
mermaidx.render(source, backend="v8")      # force V8 explicitly (raises ImportError
                                            # with an install hint if mini-racer isn't present)

V8's JIT renders noticeably faster than QuickJS-ng's interpreter-only execution — 2-4.5x in our own benchmarks, scaling up with diagram size — for byte-for-byte identical output (V8 reproduces mermaidx's own font-metrics math exactly, not an approximation). The one exception is mindmap: its cytoscape-based layout schedules an animation loop that only QuickJS-ng knows how to safely bound in a one-shot headless render (see mermaidx/engines/v8_engine.py's docstring for why) — use the default backend="quickjs" for those. backend="v8" runs its V8 isolate in a separate child process specifically so this case can't leak memory: a mindmap render still raises rather than succeeding, but the stuck process is killed outright and a fresh one takes over for the next render, with the OS guaranteeing 100% of that process's memory is reclaimed — verified to stay flat across repeated occurrences, not accumulate.

Additional backends (optional)

pip install mermaidx[rust]   # just the mmdr backends
pip install mermaidx[all]    # every optional backend + numpy support, in one go

If mmdr (a native-Rust Mermaid renderer) is installed, its backends become available too — same interface either way, and with a bonus: PDF/raw/numpy work even for backends that don't natively support them (mmdr's own Diagram.pdf() raises NotImplementedError; mermaidx's doesn't, for any backend), because every backend shares the same DiagramBase — only svg() differs per backend, everything downstream of it (PNG/PDF/raw/numpy) is the same resvg + PDF-writer pipeline for all of them:

mermaidx.backends()
# ['quickjs']                                    # nothing extra installed
# ['quickjs', 'v8']                               # mermaidx[v8] installed
# ['quickjs', 'merman', 'mermaid-rs-renderer']     # mermaidx[rust] installed

d = mermaidx.render(source, backend="merman")   # svg() comes from mmdr; everything else from mermaidx
d.pdf()                                      # works, even though mmdr's own .pdf() doesn't

CLI

# SVG to stdout (no -o needed)
mermaidx -i diagram.mermaid
cat diagram.mermaid | mermaidx -i -

# save to file (format from extension)
mermaidx -i diagram.mermaid -o diagram.svg
mermaidx -i diagram.mermaid -o diagram.png
mermaidx -i diagram.mermaid -o diagram.pdf

# size
mermaidx -i diagram.mermaid -o diagram.png -w 1200
mermaidx -i diagram.mermaid -o diagram.png --scale 2.0

# theme & background
mermaidx -i diagram.mermaid -o diagram.svg --theme dark
mermaidx -i diagram.mermaid -o diagram.png --background "#f5f5f5"

# PDF options
mermaidx -i diagram.mermaid -o diagram.pdf --pdf-format A4 --landscape --margin 1cm

# config & CSS
mermaidx -i diagram.mermaid -o diagram.svg --config config.json --css style.css

# info — Mermaid library version
mermaidx --info

# list available backends
mermaidx --list-backends

# pick a backend explicitly (quickjs/v8 always/optionally available; anything
# else requires mermaidx[rust])
mermaidx -i diagram.mermaid -o diagram.svg --backend merman

# version
mermaidx --version   # or -v

Supported Diagram Types

Everything Mermaid v11 itself supports (this bundles the real library, not a subset): flowcharts, sequence diagrams, class diagrams, state diagrams, ER diagrams, Gantt charts, pie charts, git graphs, and more.


Requirements

  • Python 3.9+
  • quickjs-ng, resvg_py, termaid (installed automatically)
  • mini-racer (optional, pip install mermaidx[v8], for the V8 engine)
  • No system packages, no Node.js, no npm, no browser

Testing

pip install -e ".[test]"
pytest tests/ -v

tests/test_online_comparison.py structurally cross-checks output against mermaid.ink (labels + aspect ratio, not pixel-diffing — two different rendering engines never match pixel-for-pixel). It needs outbound internet access and skips itself gracefully if that's unavailable or the service returns a transient error.


History

  • mmdc, powered by phasma — the original version of this project. It bundled a real (if small — around 20MB) headless browser, PhantomJS, and exposed an async Python API to match: rendering meant talking to a subprocess, so async/await genuinely mattered for concurrency.
  • 0.6.x — a full rewrite: PhantomJS's engine couldn't parse modern Mermaid (v11's ES2022+ syntax) at all, so the whole browser was replaced with mermaid.js running inside QuickJS-ng against a hand-written DOM/SVG shim, with resvg for rasterization. No subprocess left to wait on, so the API became synchronous. Three backends appeared: js (this engine), plus merman and mermaid-rs-renderer via the optional mmdr package.
  • 0.7.x, renamed to mermaidx — same engine, new name. The old name, mmdc, was identical to the official Mermaid CLI's own binary name (@mermaid-js/mermaid-cli installs a command called mmdc) — a real collision, not just a branding concern. Renamed early, while it still could be. If you have mmdc pinned anywhere (requirements.txt, a Dockerfile, CI config), switch it to mermaidx — the old name isn't maintained or published anymore.
  • Later 0.7.x — the embedded-JS-engine backend split into mermaidx/engines/ (quickjs_engine.py / v8_engine.py), and gained an optional real-V8 path (pip install mermaidx[v8], backend="v8") alongside the original QuickJS-ng one (backend="quickjs", still the default) — same mermaid.js, same output, just a choice of engine. See JS engine: QuickJS vs V8.

Acknowledgments

  • Mermaid — the actual diagramming library this project renders. mermaidx wouldn't exist without it; all it does is run the real thing somewhere a browser can't go.
  • mmdr — a native-Rust Mermaid renderer by the same author, usable as an additional backend here (see Additional backends).
  • termaid — powers ASCII/Unicode terminal output.

Contributing

  1. Fork and create a feature branch
  2. Add tests for new functionality
  3. Run pytest tests/ — all must pass
  4. Open a pull request

License

MIT — see LICENSE for details.

If mermaidx saves you from booting a headless Chrome instance a hundred times in CI, consider leaving a ⭐ on the repo — it genuinely helps others find the project.


Made by Mohammad Raziei

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mermaidx-0.9.2-py3-none-any.whl (1.7 MB view details)

Uploaded Python 3

File details

Details for the file mermaidx-0.9.2-py3-none-any.whl.

File metadata

  • Download URL: mermaidx-0.9.2-py3-none-any.whl
  • Upload date:
  • Size: 1.7 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for mermaidx-0.9.2-py3-none-any.whl
Algorithm Hash digest
SHA256 cd3576c563fb3b0bdb76f342e698bdd934ccb4f2bf04afa2dd9d7e22d9d6cef3
MD5 44742ba30743ac80a15186e6dcd74a2d
BLAKE2b-256 03c3e6b7a7dacb9b41e81a76ca8e6dfd28ccb9b7b9c49dce20ce47cf78acd261

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page