A CLI that converts declarative YAML/JSON into pixel-perfect Excalidraw PNG/SVG. Built for AI agents — write YAML, render, observe the output, fix, repeat.
- Quick Start
- CLI Reference
- YAML Schema
- Rendering Pipeline
- Agent Workflow
- Examples
- Agent Installation Guide
- Development
bun install# YAML → PNG
bun run bin/cli.ts render diagram.yaml -o output.png
# YAML → SVG
bun run bin/cli.ts render diagram.yaml -o output.svg
# Native .excalidraw files also work
bun run bin/cli.ts render drawing.excalidraw -o output.pngexcalidraw-cli render <input> -o <output> [options]
Arguments:
input Input file (.yaml, .yml, .json, .excalidraw)
Options:
-o, --output <path> Output file (.png or .svg) [required]
-t, --theme <theme> "light" (default) or "dark"
-s, --scale <number> PNG scale factor (default: 2)
-p, --padding <number> Export padding in px (default: 10)
-b, --background <color> Background color override
-q, --quiet Suppress output
-v, --verbose Debug logging
Every diagram is a YAML file with two top-level keys:
meta: # optional
title: "Diagram Title"
theme: light # light | dark
background: "#f8f9fa" # any hex color
elements: # required, list of elements
- type: rectangle
id: box1
...| Type | Description |
|---|---|
rectangle |
Rectangle with optional label |
ellipse |
Ellipse with optional label |
diamond |
Diamond with optional label |
text |
Standalone text |
arrow |
Arrow or labeled connector |
line |
Line (no label support — use arrow with null arrowheads instead) |
freedraw |
Freehand drawing |
frame |
Frame container |
image |
Image placeholder |
All elements support:
id: my_id # string, auto-generated if omitted
x: 100 # position (default: 0)
y: 200
width: 200 # size (default: 200x100 for shapes)
height: 100
strokeColor: "#1e1e1e"
backgroundColor: "#a5d8ff"
fillStyle: solid # solid | hachure | cross-hatch
strokeWidth: 2
strokeStyle: solid # solid | dashed | dotted
roughness: 1 # 0 (clean) to 2 (sketchy)
opacity: 100 # 0-100
rotation: 0 # degrees
groupIds: [g1] # grouping- type: rectangle
id: server
x: 100
y: 100
width: 200
height: 80
backgroundColor: "#a5d8ff"
fillStyle: solid
strokeColor: "#1971c2"
label:
text: "API Server"
fontSize: 16
fontFamily: 1 # 1=Virgil, 2=Helvetica, 3=Cascadia, 5=Excalifont
textAlign: center # left | center | right
verticalAlign: middle # top | middle | bottom
strokeColor: "#1e1e1e"- type: text
id: title
x: 100
y: 0
text: "System Architecture"
fontSize: 22
fontFamily: 1
textAlign: left
strokeColor: "#343a40"Width/height are auto-calculated from text content if omitted.
Three routing methods:
- type: arrow
id: flow1
start: { id: source_shape }
end: { id: target_shape }The engine auto-computes connection points based on relative shape positions. When multiple arrows share the same edge, they distribute evenly (1/3, 1/4 positions).
- type: arrow
id: flow2
x: 300 # base position
y: 140
points: [[0, 0], [0, 120]] # relative to (x, y)Use explicit points when auto-routing produces bad results (wrong edge, bad angle, passing through other shapes).
For connectors that need a label but no arrowheads, use type: arrow with null arrowheads:
- type: arrow
id: couple
x: 200
y: 112
points: [[0, 0], [60, 0]]
strokeColor: "#e03131"
strokeWidth: 2
strokeStyle: dashed # optional: dashed for informal relationships
startArrowhead: null # renders like a line
endArrowhead: null
label:
text: "married"
fontSize: 12
strokeColor: "#e03131"Never use
type: linewith labels — it renders broken elliptical containers. Always usetype: arrowwith null arrowheads.
startArrowhead: arrow # arrow | bar | dot | triangle | null
endArrowhead: arrow # default: "arrow" for type: arrowYAML/JSON -> Zod validation -> Skeleton builder -> convertToExcalidrawElements (jsdom)
-> Puppeteer (Chromium) -> exportToBlob (PNG) / exportToSvg (SVG)
- Rendering runs in a real Chromium browser via Puppeteer
- Uses
@excalidraw/utilsfor pixel-perfect output identical to excalidraw.com - All fonts (Virgil, Excalifont, etc.) are bundled — no external loading needed
This CLI is built for agents to create diagrams through an iterative loop. The full workflow spec is in docs/SKILL.md.
Never output a complete diagram in one shot. Build iteratively:
- Phase 1 — Nodes only: Place all shapes with NO arrows. Render, observe, fix spacing/alignment.
- Phase 2 — Connections: Add arrows one group at a time. Render after each group, fix routing issues.
- Phase 3 — Polish: Add titles, annotations, labels. Final render and fine-tune.
After each render, read the PNG output and check: crossings, overlaps, arrow routing, label readability, spacing, alignment. Fix any issue before proceeding.
- No crossings or overlaps — arrows must not cross each other or pass through unrelated shapes. Rectangles must not overlap.
- Arrow labels need breathing room — at least 20px of line on each side of a label. At least 50px gap between shapes for couple connectors.
- Prefer edge centers — arrows connect to the center of the nearest edge. Multiple arrows on the same edge distribute evenly (N arrows -> N+1 equal divisions).
- Choose edge by angle — if the arrow angle is too shallow relative to an edge (< 20deg), connect to an adjacent edge instead.
- Consistent spacing — 60-100px horizontal gaps, 140-170px vertical gaps between layers.
Colors (pick <= 4 per diagram):
| Category | Fill | Stroke |
|---|---|---|
| Blue | #a5d8ff |
#1971c2 |
| Pink | #fcc2d7 |
#c2255c |
| Orange | #ffec99 |
#e67700 |
| Purple | #d0bfff |
#7048e8 |
| Green | #b2f2bb |
#2f9e44 |
| Cyan | #99e9f2 |
#0c8599 |
Font sizes: Title 20-22, Node labels 15-16, Annotations 13-14, Arrow labels 12-13
Canvas: 900-1200px wide
meta:
theme: light
elements:
- type: rectangle
id: a
x: 100
y: 50
width: 200
height: 80
backgroundColor: "#a5d8ff"
fillStyle: solid
label:
text: "Box A"
- type: rectangle
id: b
x: 100
y: 250
width: 200
height: 80
backgroundColor: "#ffec99"
fillStyle: solid
label:
text: "Box B"
- type: arrow
id: arr
start: { id: a }
end: { id: b }See test/fixtures/game-of-thrones.yaml — demonstrates:
- Multi-generational layout with 3 family groups
- Color-coded houses (purple, blue, orange, green)
- Couple connectors with CJK labels
- Mixed routing: binding arrows for vertical flows, explicit points for precise control
- Annotations and text labels
See test/fixtures/project-architecture.yaml — demonstrates:
- 7-layer vertical flow
- Auto-routed binding arrows with edge spread distribution
- Side connections
See docs/AGENT_SETUP.md for a step-by-step guide on how to install excalidraw-cli as a skill for any Claude Code agent.
bun test # run tests
bun run bin/cli.ts # run CLI