Clean, deterministic, browser-free renderer for .excalidraw files. Pure Python + cairosvg. No Node, no headless browser, no node-canvas.
pip install excalidraw-render
excalidraw-render diagram.excalidraw # → diagram.png
excalidraw-render diagram.excalidraw -f svg # → diagram.svg
excalidraw-render diagram.excalidraw -f pdf # → diagram.pdf (vector)
excalidraw-render diagram.excalidraw --width 1200 # PNG at 1200px wide
excalidraw-render ./docs/ # batch: every .excalidraw → .png
excalidraw-render ./docs/ --watch # re-render on every saveThe Excalidraw ecosystem's existing exporters either need Node + node-canvas (excalidraw_export) or a headless browser running React (@excalidraw/excalidraw). Both are heavy, fragile in CI, and slow to start.
excalidraw-render is Python + cairosvg. Single-digit-megabyte install, instant startup, no native canvas libs, no Chromium. Useful for:
- Static-site generators (Hugo, MkDocs, Sphinx) baking
.excalidrawto PNG at build time - CI/screenshot pipelines where pulling Chromium is overkill
- Doc + slide generators that want predictable, deterministic vector output
- Terminal viewers (Kitty, iTerm2, Sixel) — on the roadmap
Excalidraw's signature squiggly look comes from roughjs, a JavaScript library with no native Python port. excalidraw-render produces clean vector output instead. This is a deliberate trade-off — clean output is what most doc / slide / report pipelines actually want, and skipping roughjs removes the heaviest dependency.
A pure-Python roughjs port (pyroughjs) is on the roadmap. Until then, if you need the hand-drawn look, use Excalidraw's official export.
excalidraw-render |
excalidraw_export |
@excalidraw/excalidraw |
|
|---|---|---|---|
| Language | Python | Node | React + Node |
| Hand-drawn (roughjs) | No (planned) | Yes | Yes |
| PNG output | Yes | via rsvg-convert |
Yes |
| SVG output | Yes | Yes | Yes |
| PDF output | Yes | via rsvg-convert |
No |
| Headless browser needed | No | No | Yes |
| Native canvas / image libs | No | Yes (node-canvas) |
No |
| Install size | ~10 MB | ~150 MB | Depends |
| Batch / watch mode | Yes | No | No |
| Terminal protocols | Planned (iTerm / Kitty / Sixel) | No | No |
pip install excalidraw-renderOn Linux you may need libcairo2:
sudo apt-get install libcairo2 # Debian / Ubuntu
sudo dnf install cairo # Fedora
sudo pacman -S cairo # ArchOn macOS, Cairo is typically already present via Homebrew. If not:
brew install cairo# Single file → PNG next to source
excalidraw-render diagram.excalidraw
# Specify output + format
excalidraw-render diagram.excalidraw -o /tmp/out.svg -f svg
# Higher resolution PNG
excalidraw-render diagram.excalidraw --width 1600
# Or scale instead of fixed width
excalidraw-render diagram.excalidraw --scale 2.0
# Transparent background
excalidraw-render diagram.excalidraw --no-background
# Vector PDF / compressed JPEG
excalidraw-render diagram.excalidraw -f pdf
excalidraw-render diagram.excalidraw -f jpg --quality 85
# Batch mode: every .excalidraw in a directory
excalidraw-render ./docs/diagrams/ -o ./public/diagrams/
# Watch mode: re-render on every save (single file or directory)
excalidraw-render ./docs/diagrams/ --watchfrom excalidraw_render.render import load_scene, render_svg, render_png, render_pdf, render_jpg
scene = load_scene("diagram.excalidraw")
# SVG as a string
svg = render_svg(scene)
# PNG to a file
render_png(scene, "diagram.png", width=1200)
# Vector PDF / JPEG
render_pdf(scene, "diagram.pdf")
render_jpg(scene, "diagram.jpg", quality=85)
# PNG to a binary stream
import io
buf = io.BytesIO()
render_png(scene, buf, scale=2.0)Formats: PNG, SVG, vector PDF, JPEG.
Elements: rectangle, ellipse, diamond, arrow, line, text, freedraw, image, frame.
Arrowheads: arrow, triangle, triangle_outline, bar, dot, diamond, diamond_outline, crowfoot_one, crowfoot_many, crowfoot_one_or_many.
Styling: stroke color/width, fill color, fill patterns (hachure, cross-hatch, zigzag, dots, dashed — clean SVG-pattern approximations of the roughjs originals), stroke style (solid/dashed/dotted), opacity, per-element rotation.
Text: multi-line, text-align (left/center/right), vertical-align (top/middle/bottom), Excalidraw font families mapped to web-safe fonts. Container-bound text (labels inside rectangles, ellipses, diamonds) is positioned with Excalidraw's own padding-aware layout algorithm and rotates with its container.
Freedraw: smooth path via Catmull-Rom → cubic Bezier conversion.
Image: embedded raster data from the scene's files dict.
Frame: dashed boundary box with optional label.
Coverage matrix: docs/elements.md.
See ROADMAP.md for the full release ladder to 1.0. Highlights:
- Roughness / hand-drawn look (needs roughjs port — planned for 0.7.0)
- Markdown preprocessor subcommand (0.5.0)
- Terminal output (iTerm / Kitty / Sixel) (0.6.0)
This is pre-alpha and breaking changes between minor versions are likely until v1.0. Issues, ideas, and PRs welcome — please file at https://github.com/shivama205/excalidraw-render/issues.
Local dev:
git clone https://github.com/shivama205/excalidraw-render
cd excalidraw-render
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/pytest
.venv/bin/ruff check src tests
.venv/bin/mypy srcMIT — see LICENSE.
