Add content to existing Obsidian Canvas files. Supports all node types: images (with auto aspect ratio detection), text cards, PDFs, wiki notes, web links, Mermaid diagrams, SVGs, GIFs, AI-generated images via banana. Also adds zones (groups), edges between nodes, and imports recent banana images. Triggers on: canvas add, add to canvas, put on canvas, canvas zone, canvas connect, canvas from banana, add image to canvas, add text to canvas.
Read ../canvas/references/canvas-spec.md for the full JSON format before any edit.
Read ../canvas/references/performance-guide.md for node limits and constraints.
For every add operation:
nodes and edges arrays.[type]-[slug]-[unix-timestamp].nodes array (after any groups — z-index ordering).python3 scripts/canvas_validate.py <path>/canvas add image [path or url])Resolve the image:
http): download with curl -sL [url] -o [media_dir]/[filename].
Derive filename from URL path, or use img-[timestamp].jpg if unclear.cp [path] [media_dir]/Detect aspect ratio:
python3 -c "from PIL import Image; img=Image.open('[path]'); print(img.width, img.height)"
# or fallback
identify -format '%w %h' [path]
Map to the sizing table in ../canvas/references/canvas-spec.md (7 ratios + PDF + fallback).
Create file node with calculated dimensions. Position using auto-layout.
/canvas add text [content]){
"id": "text-[slug]-[timestamp]",
"type": "text",
"text": "[content]",
"x": 0, "y": 0,
"width": 300, "height": 120
}
For multi-line content, estimate height: count newlines, multiply by 24, add 40px padding. Minimum 120px.
/canvas add pdf [path])Copy to [media_dir]/ if outside canvas dir. Fixed size: width=400, height=520.
/canvas add note [wiki-page])"type": "file" with vault-relative path. Not "type": "link" — that's for URLs only./canvas add link [url]){
"id": "link-[slug]-[timestamp]",
"type": "link",
"url": "[url]",
"x": 0, "y": 0,
"width": 400, "height": 120
}
Obsidian fetches Open Graph preview automatically.
/canvas add mermaid [code])Mermaid renders natively in Obsidian text nodes. Wrap in a fenced code block:
{
"id": "text-mermaid-[timestamp]",
"type": "text",
"text": "```mermaid\n[code]\n```",
"x": 0, "y": 0,
"width": 500, "height": 400,
"color": "5"
}
Wider text nodes work better for Mermaid (min 400px wide, 300px tall).
/canvas add svg [description or path])/svg skill to generate, then add the output file.<img> — no interactivity. Must have viewBox attribute./canvas add gif [description or path])/claude-gif-generate skill, then add the output./canvas add banana [prompt]).recent-images.txt in the canvas directory./canvas zone [name] [color])-80 if canvas is empty (consistent with starter canvas layout where title is at y=-300 and default zone at y=-140):
if not canvas_nodes:
max_y = -80
else:
max_y = max(n["y"] + n.get("height", 0) for n in canvas_nodes) + 60
{
"id": "zone-[slug]-[timestamp]",
"type": "group",
"label": "[name]",
"x": -400,
"y": "[max_y]",
"width": 1000,
"height": 400,
"color": "[color or '4']"
}
Valid colors: "1"=red "2"=orange "3"=yellow "4"=green "5"=cyan "6"=purple
/canvas connect [from] [to] [label])from and to nodes by ID, label text, or partial match.{
"id": "e-[from-slug]-[to-slug]-[timestamp]",
"fromNode": "[from-id]",
"toNode": "[to-id]",
"toEnd": "arrow"
}
fromSide/toSide for auto-routing (better results).label if provided./canvas from banana)[canvas_dir]/.recent-images.txt for recently logged image paths.find [media_dir] -name "*.png" -o -name "*.jpg" -newer /tmp/ten-min-ago
_attachments/images/canvas/.canvases/assets/