tldraw-desktop
tldraw Desktop Canvas API
Programmatically create and modify diagrams on the tldraw desktop app via its local HTTP API at http://localhost:7236.
IMPORTANT: Always use curl for all API calls. Do NOT use fetch, wget, or any other HTTP client.
Workflow
1. curl -s http://localhost:7236/api/doc → list open docs (get DOC_ID)
2. curl -s http://localhost:7236/api/doc/{id}/shapes → read current shapes
3. curl -s -X POST http://localhost:7236/api/doc/{id}/actions → create/modify shapes
4. curl -s http://localhost:7236/api/doc/{id}/screenshot -o f.jpg → verify result visually
Always verify your work with a screenshot after making changes. Prefer small, incremental batches.
Overlap Check — MANDATORY after every batch of changes
After creating or modifying shapes, you MUST:
- Take a screenshot and visually inspect for overlapping shapes, crossing arrows, or crowded text
- If overlaps or readability issues are found, fix them using
place,move,stack,align, ordistribute - Take another screenshot to confirm the fix
digraph overlap_check {
"Make changes" -> "Screenshot";
"Screenshot" -> "Overlaps or crowding?" [label="inspect"];
"Overlaps or crowding?" -> "Fix layout" [label="yes"];
"Fix layout" -> "Screenshot";
"Overlaps or crowding?" -> "Done" [label="no, clean"];
}
Common overlap fixes:
- Shapes stacked on top of each other → use
stackwithgap: 80orplacewithsideOffset: 80 - Arrows crossing through unrelated shapes →
movethe blocking shape out of the path, or usekind: "elbow"for right-angle routing - Text labels overlapping → make shapes wider (
resizeorupdatewith largerw) or increase gap between shapes - Dense diagrams → split into rows/columns using
stack+align, or increase spacing withdistribute
Quick Reference
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/doc |
List open docs. Filter: ?name= |
| GET | /api/doc/:id/shapes |
All shapes on current page |
| GET | /api/doc/:id/screenshot |
JPEG capture. ?size=small|medium|large|full |
| POST | /api/doc/:id/actions |
Structured actions (see below) |
| POST | /api/doc/:id/exec |
Run arbitrary JS against tldraw Editor |
Shape Types
| Type | Key Properties |
|---|---|
rectangle, ellipse, triangle, diamond, hexagon, pill, cloud, star, heart, pentagon, octagon, x-box, check-box, trapezoid, parallelogram-right/left, fat-arrow-right/left/up/down |
shapeId, x, y, w, h, color, fill, text, note |
text |
shapeId, x, y, anchor, color, fontSize, maxWidth, text |
arrow |
shapeId, fromId, toId, color, kind (arc|elbow), text, bend |
line |
shapeId, x1, y1, x2, y2, color |
note |
shapeId, x, y, color, text (sticky note) |
pen |
shapeId, points:[{x,y}], color, style (smooth|straight), closed, fill |
Colors: red, light-red, green, light-green, blue, light-blue, orange, yellow, black, violet, light-violet, grey, white Fill: none, tint, background, solid, pattern Anchor (text): top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right
Actions
POST to /api/doc/:id/actions with {"actions": [...]}. All actions in one request = single undo step.
| Action | Payload |
|---|---|
create |
{"_type":"create","shape":{...}} |
update |
{"_type":"update","shape":{"shapeId":"id","_type":"rect",...changed props}} |
delete |
{"_type":"delete","shapeId":"id"} |
clear |
{"_type":"clear"} |
label |
{"_type":"label","shapeId":"id","text":"new text"} |
move |
{"_type":"move","shapeId":"id","x":N,"y":N,"anchor":"center"} |
place |
{"_type":"place","shapeId":"id","referenceShapeId":"ref","side":"right","align":"center","sideOffset":20} |
align |
{"_type":"align","shapeIds":[...],"alignment":"center-horizontal"} |
distribute |
{"_type":"distribute","shapeIds":[...],"direction":"horizontal"} |
stack |
{"_type":"stack","shapeIds":[...],"direction":"vertical","gap":20} |
resize |
{"_type":"resize","shapeIds":[...],"scaleX":2,"scaleY":1.5} |
rotate |
{"_type":"rotate","shapeIds":[...],"degrees":45} |
bringToFront |
{"_type":"bringToFront","shapeIds":[...]} |
sendToBack |
{"_type":"sendToBack","shapeIds":[...]} |
pen |
{"_type":"pen","shapeId":"id","points":[{"x":0,"y":0},...],"color":"black","style":"smooth"} |
place sides: top, bottom, left, right align options: top, center-vertical, bottom, left, center-horizontal, right
Best Practices
- Always use
curlfor all HTTP calls — no exceptions - Default shape size: ~300x200 with ~200 gap between shapes
- Connect shapes with arrows: use
fromId/toId— arrows stay attached when shapes move - Prefer high-level actions (
place,stack,align) over manual coordinate math - Batch actions in a single POST for atomic undo/redo
- Use
placeto position shapes relative to each other instead of calculating coordinates - Default styles: black, medium, draw style, centered text, no fill — unless there's a reason to change
- Fill recommendations:
nonefor outlines,solidfor tinted backgrounds - Screenshot after changes to verify layout and check for overlaps
- Assign readable shapeIds (e.g.,
"auth-box","db-node") for easier reference - Large diagrams (7+ shapes): build in stages — create a few shapes, screenshot, adjust layout, then add more
Example: Creating a Flowchart
DOC_ID="your-doc-id"
BASE="http://localhost:7236/api/doc/$DOC_ID"
# Create shapes + connect with arrows in one call
curl -s -X POST "$BASE/actions" \
-H "Content-Type: application/json" \
-d '{
"actions": [
{"_type":"create","shape":{"_type":"rectangle","shapeId":"start","x":100,"y":100,"w":300,"h":200,"text":"Start"}},
{"_type":"create","shape":{"_type":"diamond","shapeId":"decision","x":100,"y":400,"w":300,"h":200,"text":"Condition?"}},
{"_type":"create","shape":{"_type":"rectangle","shapeId":"end","x":100,"y":700,"w":300,"h":200,"text":"End"}},
{"_type":"create","shape":{"_type":"arrow","shapeId":"a1","fromId":"start","toId":"decision"}},
{"_type":"create","shape":{"_type":"arrow","shapeId":"a2","fromId":"decision","toId":"end","text":"Yes"}}
]
}'
# MANDATORY: Screenshot to check for overlaps
curl -s "$BASE/screenshot?size=medium" -o screenshot.jpg
# Inspect the screenshot — if shapes overlap or arrows cross, fix with place/move/stack
Exec API
For advanced operations not covered by structured actions:
curl -s -X POST "$BASE/exec" \
-H "Content-Type: application/json" \
-d '{"code": "return editor.getCurrentPageShapeIds().size"}'
Full SDK docs available at: curl -s http://localhost:7236/api/llms