Excalidraw diagrams (via the Excalidraw+ MCP)
Build scenes that read as a deliberate visual argument, not "boxes with text,"
and get them right the first time by knowing the MCP's non-obvious behaviours up
front. The element JSON here is standard Excalidraw, so it also applies if you ever
generate .excalidraw files directly — but this skill targets the hosted
Excalidraw+ MCP (shareable scenes, collections), which is the usual case.
The workflow
- Plan the structure first (see
reference/layout.md). Pick the family
(workflow / architecture / timeline / hierarchy / comparison / cycle / slides),
the reading direction, and the regions — before placing anything. The test:
if you deleted every label, would the layout alone still convey the idea?
- Call
read_excalidraw_format once before your first write this session. It's
the MCP's own normative element guide (occasionally updated) and the server
expects it. reference/elements.md is your fast working subset.
- Create or target a scene. New:
create_scene (needs a collectionId — get
it from list_collections). Existing: find it via list_scenes /
get_workspace; to edit an earlier one, get real element IDs with
search_scene_content. The shareable link is
https://app.excalidraw.com/s/<workspace_id>/<scene_id> — both IDs come back in
the create_scene metadata (workspace and id); give this link to the user.
- Add content with
edit_scene_content — one coherent diagram per call.
Build in placement order: regions/frames → containers → primary nodes →
secondary nodes → labels → arrows (bound, once nodes are stable) → accents.
- Add icons where they help (
reference/library-icons.md + the bundled
script) — real Postgres/React/Docker/AWS icons, inserted via the MCP.
- Verify — this is mandatory, not optional (see "Verifying" below). Screenshot
for layout,
search_scene_content for text/geometry, fix, repeat.
Non-negotiables (each one silently ruins a diagram)
These are the traps that make a diagram look broken or fall apart on the next edit.
Full explanations in reference/mcp-gotchas.md — internalize these five:
- Screenshots don't render text. A screenshot that looks "empty" usually has all
its labels. Never judge text presence/content from a screenshot — use
search_scene_content.
- New elements can land under existing fills (the server assigns indices
non-monotonically, even on a fresh delete-free build). Icons placed on cards, or
cards placed in a box, can render invisibly under the opaque fill — often
inconsistently (some show, some don't). Simplest robust fix: give grouping
containers AND cards
backgroundColor:"transparent" (keep the coloured border) —
a transparent fill can never occlude, so z-order stops mattering for content inside
a box, and a white card on a white canvas looked identical anyway. Only fall back to
an explicit high index (read the current max via search_scene_content, don't
trust a literal like "b01") when you need one opaque element atop another. See
reference/mcp-gotchas.md.
textAlign:"center" makes the x you pass the CENTER of a standalone label
(not the left edge). To center under a node at center X, pass x: X.
- Bound labels are always vertically centered in their shape. For an
icon-above-label block, use a standalone label positioned yourself, not a bound
label.
- Arrows pointing at shapes must be bound (
startBinding/endBinding, mode
"inside"), or they detach on the next edit. A bound arrow label (label:{}) is
created only at add time — adding one via update silently no-ops (added:0), so
re-add an arrow to (re)label it, and read the label as the arrow's labelText (it's
invisible to a types:["text"] search). You can't delete a shape and its bound arrow
in the same call — delete the arrow first.
Verifying (do not claim done without this)
A green edit_scene_content result is not verification. After building:
take_screenshot → check layout, spacing, overlap, arrow routing, colour, icon
shapes. (Not text — it won't render.)
search_scene_content → confirm every label exists with the right text, and
that positions/alignment are what you intended (e.g. equal top/bottom margins for
a centered block; shared y across a row).
- Fix and re-verify until both pass. Typical: 2–4 iterations.
Centering, alignment & icons — the fiddly parts
The bundled script does the error-prone math and the icon extraction. Run it from
the skill directory:
# centering: where to put an icon + standalone label as one centered block in a box
python scripts/excalidraw_tools.py place --box X Y W H --icon-h H --font F
# find an icon pack, list its items, extract one positioned icon as add-JSON
python scripts/excalidraw_tools.py catalog <keyword>
python scripts/excalidraw_tools.py list <author/name.excalidrawlib>
python scripts/excalidraw_tools.py icon <lib> --item <idx|name> --at <cx> <top_y> --height <h> [--swap OLD=NEW]
place and icon both take --at as center-x, top-y (not the box's top-left
x,y) — feed icon --at the exact center_x/top_y that place printed.
Row/column alignment is just shared y+height (row) or x+width (column) with
a constant step — keeping those exact is what separates intentional from
hand-nudged. Details and the reasoning in reference/layout.md.
Reference map
Read the file that matches the task; don't load them all up front.
reference/mcp-gotchas.md — the MCP quirks above, in full. Read before any real
scene work — these cause the most wasted time.
reference/elements.md — element fields, fonts, semantic color palette, arrow
bindings, frames/slides, charts. Your working reference while building.
reference/layout.md — planning, spacing, centering/alignment math, and which
structure to choose for a given idea (with a decision table).
reference/library-icons.md — finding and inserting real community icons via the
MCP; identifying unnamed icons; recommended packs.
scripts/excalidraw_tools.py — catalog / list / icon / place. Run
--help for any subcommand.
Professional vs hand-drawn
Excalidraw defaults to a hand-drawn look (roughness:1, Excalifont). For clean,
technical diagrams set roughness:0, use fontFamily:8 (mono) or 7 (titles),
and lean on gray/blue with one accent. For approachable/whiteboard diagrams, keep
the defaults. Match the register to the audience.
1---2name: excalidraw-diagrams3description: Use when creating, editing, or updating any Excalidraw scene, diagram, flowchart, architecture/system diagram, mind map, wireframe, timeline, or slide deck through the Excalidraw+ MCP — whenever the user says "excalidraw", "draw a diagram", "make a flowchart/architecture diagram", "put this on a canvas", or wants recognizable tech/logo icons (Postgres, React, Docker, AWS…) in a diagram. Covers the MCP workflow, element format, layout/centering/alignment, real library icons, and the MCP quirks that silently break diagrams (invisible text, z-order, bindings). Use even for a "simple" diagram — the gotchas bite there too.4---56# Excalidraw diagrams (via the Excalidraw+ MCP)78Build scenes that read as a deliberate **visual argument**, not "boxes with text,"9and get them right the first time by knowing the MCP's non-obvious behaviours up10front. The element JSON here is standard Excalidraw, so it also applies if you ever11generate `.excalidraw` files directly — but this skill targets the hosted12**Excalidraw+ MCP** (shareable scenes, collections), which is the usual case.1314## The workflow15161. **Plan the structure first** (see `reference/layout.md`). Pick the family17 (workflow / architecture / timeline / hierarchy / comparison / cycle / slides),18 the reading direction, and the regions — *before* placing anything. The test:19 if you deleted every label, would the layout alone still convey the idea?202. **Call `read_excalidraw_format` once** before your first write this session. It's21 the MCP's own normative element guide (occasionally updated) and the server22 expects it. `reference/elements.md` is your fast working subset.233. **Create or target a scene.** New: `create_scene` (needs a `collectionId` — get24 it from `list_collections`). Existing: find it via `list_scenes` /25 `get_workspace`; to edit an earlier one, get real element IDs with26 `search_scene_content`. The shareable link is27 `https://app.excalidraw.com/s/<workspace_id>/<scene_id>` — both IDs come back in28 the `create_scene` metadata (`workspace` and `id`); give this link to the user.294. **Add content** with `edit_scene_content` — one coherent diagram per call.30 Build in placement order: regions/frames → containers → primary nodes →31 secondary nodes → labels → arrows (bound, once nodes are stable) → accents.325. **Add icons** where they help (`reference/library-icons.md` + the bundled33 script) — real Postgres/React/Docker/AWS icons, inserted via the MCP.346. **Verify — this is mandatory, not optional (see "Verifying" below).** Screenshot35 for layout, `search_scene_content` for text/geometry, fix, repeat.3637## Non-negotiables (each one silently ruins a diagram)3839These are the traps that make a diagram look broken or fall apart on the next edit.40Full explanations in `reference/mcp-gotchas.md` — internalize these five:4142- **Screenshots don't render text.** A screenshot that looks "empty" usually has all43 its labels. Never judge text presence/content from a screenshot — use44 `search_scene_content`.45- **New elements can land *under* existing fills** (the server assigns indices46 non-monotonically, even on a fresh delete-free build). Icons placed on cards, or47 cards placed in a box, can render invisibly under the opaque fill — often48 *inconsistently* (some show, some don't). **Simplest robust fix: give grouping49 containers AND cards `backgroundColor:"transparent"`** (keep the coloured border) —50 a transparent fill can never occlude, so z-order stops mattering for content inside51 a box, and a white card on a white canvas looked identical anyway. Only fall back to52 an explicit high `index` (read the current max via `search_scene_content`, don't53 trust a literal like `"b01"`) when you need one *opaque* element atop another. See54 `reference/mcp-gotchas.md`.55- **`textAlign:"center"` makes the `x` you pass the CENTER** of a standalone label56 (not the left edge). To center under a node at center X, pass `x: X`.57- **Bound labels are always vertically centered** in their shape. For an58 icon-above-label block, use a *standalone* label positioned yourself, not a bound59 `label`.60- **Arrows pointing at shapes must be bound** (`startBinding`/`endBinding`, mode61 `"inside"`), or they detach on the next edit. A **bound arrow label** (`label:{}`) is62 created only at `add` time — adding one via `update` silently no-ops (`added:0`), so63 re-add an arrow to (re)label it, and read the label as the arrow's `labelText` (it's64 invisible to a `types:["text"]` search). You can't delete a shape and its bound arrow65 in the same call — delete the arrow first.6667## Verifying (do not claim done without this)6869A green `edit_scene_content` result is not verification. After building:701. `take_screenshot` → check layout, spacing, overlap, arrow routing, colour, icon71 shapes. (Not text — it won't render.)722. `search_scene_content` → confirm every label exists with the right `text`, and73 that positions/alignment are what you intended (e.g. equal top/bottom margins for74 a centered block; shared `y` across a row).753. Fix and re-verify until both pass. Typical: 2–4 iterations.7677## Centering, alignment & icons — the fiddly parts7879The bundled script does the error-prone math and the icon extraction. Run it from80the skill directory:8182```bash83# centering: where to put an icon + standalone label as one centered block in a box84python scripts/excalidraw_tools.py place --box X Y W H --icon-h H --font F8586# find an icon pack, list its items, extract one positioned icon as add-JSON87python scripts/excalidraw_tools.py catalog <keyword>88python scripts/excalidraw_tools.py list <author/name.excalidrawlib>89python scripts/excalidraw_tools.py icon <lib> --item <idx|name> --at <cx> <top_y> --height <h> [--swap OLD=NEW]90```9192`place` and `icon` both take `--at` as **center-x, top-y** (not the box's top-left93`x,y`) — feed `icon --at` the exact `center_x`/`top_y` that `place` printed.9495Row/column alignment is just shared `y`+`height` (row) or `x`+`width` (column) with96a constant step — keeping those exact is what separates intentional from97hand-nudged. Details and the reasoning in `reference/layout.md`.9899## Reference map100101Read the file that matches the task; don't load them all up front.102103- `reference/mcp-gotchas.md` — the MCP quirks above, in full. **Read before any real104 scene work** — these cause the most wasted time.105- `reference/elements.md` — element fields, fonts, semantic color palette, arrow106 bindings, frames/slides, charts. Your working reference while building.107- `reference/layout.md` — planning, spacing, centering/alignment math, and which108 structure to choose for a given idea (with a decision table).109- `reference/library-icons.md` — finding and inserting real community icons via the110 MCP; identifying unnamed icons; recommended packs.111- `scripts/excalidraw_tools.py` — `catalog` / `list` / `icon` / `place`. Run112 `--help` for any subcommand.113114## Professional vs hand-drawn115116Excalidraw defaults to a hand-drawn look (`roughness:1`, Excalifont). For clean,117technical diagrams set `roughness:0`, use `fontFamily:8` (mono) or `7` (titles),118and lean on gray/blue with one accent. For approachable/whiteboard diagrams, keep119the defaults. Match the register to the audience.