skills/tldraw-creator/SKILL.md
Programmatically create tldraw whiteboards and visualize them with a self-hosted tldraw instance. Create boards with shapes, text, and connectors, then deploy to a self-hosted server for collaborative editing and gallery management.
npx skillsauth add arisng/github-copilot-fc tldraw-creatorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Create and manage tldraw whiteboards programmatically, deploy self-hosted instances, and build collaborative whiteboarding workflows.
tldraw-creator enables agents to:
@tldraw/store API (shapes, text, connectors, frames).tldraw JSON documents@tldraw packages)Note: As of tldraw 3.x all tldraw functionality is bundled in the single
tldrawpackage. The individual@tldraw/store,@tldraw/tlschema,@tldraw/ui, and@tldraw/editorsub-packages are still available but thetldrawmeta-package is the recommended entry point.
# Recommended: single package (tldraw 3.x)
npm install tldraw react react-dom
# Optional: individual sub-packages for advanced use
npm install @tldraw/store @tldraw/tlschema
npm install pg yjs y-websocket # For server + persistence
npm install express cors # For API server (optional)
tldraw ships dual CJS/ESM code. When using Vite, add lodash.isequalwith to optimizeDeps.include to avoid a missing-default-export error at runtime:
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
optimizeDeps: {
include: ['lodash.isequalwith', 'lodash.isequal'],
},
})
Create .env file in your skill workspace root or pass as process variables:
# Programmatic Board Creation
TLDRAW_EXPORT_FORMAT=json # json | svg | png (default: json)
# Self-Hosted Server
TLDRAW_SERVER_PORT=3000
TLDRAW_DATABASE_URL=postgresql://user:password@localhost:5432/tldraw
TLDRAW_WEBSOCKET_URL=wss://tldraw.example.com
TLDRAW_NODE_ENV=development # development | production
# Optional: Cloud Storage
TLDRAW_S3_BUCKET=my-tldraw-boards
TLDRAW_S3_REGION=us-east-1
Purpose: Programmatically generate a tldraw whiteboard with shapes, text, and connectors.
Input Schema:
{
"name": "my-board",
"title": "Project Architecture",
"shapes": [
{
"id": "shape-1",
"type": "geo",
"x": 100,
"y": 100,
"props": {
"w": 200,
"h": 100,
"geo": "rectangle",
"color": "blue",
"richText": { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Frontend" }] }] }
}
}
],
"theme": "dark"
}
tldraw 3.x: shape labels use
richText(ProseMirror document), nottext. UsetoRichText(str)from thetldrawpackage to convert plain strings.
Output Schema:
{
"success": true,
"boardId": "doc_abc123",
"boardName": "my-board",
"exportPath": "/path/to/my-board.tldraw",
"boardJson": { "/* TLStore snapshot */" }
}
Implementation: See scripts/create-board.js
Purpose: Set up a self-hosted tldraw instance for visualization and collaboration.
Input Schema:
{
"deploymentType": "docker",
"persistenceType": "postgresql",
"databaseUrl": "postgresql://tldraw:password@localhost:5432/tldraw",
"serverPort": 3000,
"enableWebSocket": true
}
Output Schema:
{
"success": true,
"serverUrl": "http://localhost:3000",
"status": "running",
"containerName": "tldraw-server",
"logs": "Server started..."
}
Implementation: See scripts/deploy-server.js
Purpose: Upload a board JSON to a running self-hosted instance and get a visualization URL.
Input Schema:
{
"serverUrl": "http://localhost:3000",
"boardJson": { "/* TLStore snapshot */" },
"boardName": "my-board"
}
Output Schema:
{
"success": true,
"boardId": "doc_abc123",
"viewUrl": "http://localhost:3000/room/doc_abc123",
"editUrl": "http://localhost:3000/edit/doc_abc123"
}
Implementation: See scripts/import-board.js
Purpose: Get a list of all boards on a self-hosted instance (for gallery/management).
Input Schema:
{
"serverUrl": "http://localhost:3000",
"limit": 20,
"sortBy": "created_at"
}
Output Schema:
{
"success": true,
"boards": [
{
"id": "doc_abc123",
"name": "Project Architecture",
"createdAt": "2026-04-26T12:00:00Z",
"updatedAt": "2026-04-26T14:30:00Z",
"url": "http://localhost:3000/room/doc_abc123"
}
]
}
Implementation: See scripts/list-boards.js
User Request: "Create a diagram with 3 interconnected components and deploy it to a whiteboard"
Agent Flow:
1. Agent calls create-board with:
- 3 geo shapes (rectangles)
- 2 arrow connectors
- Labels for each component
2. Agent calls deploy-server (if not already running)
3. Agent calls import-board-to-server with the generated board
4. Agent returns URL: http://localhost:3000/room/doc_abc123
User Request: "Build a swimlane diagram for a user onboarding flow"
Agent Flow:
1. Agent designs shapes:
- Frames for each swimlane (user, backend, email service)
- Text boxes for steps
- Arrows showing flow direction
2. Agent calls create-board
3. Board is saved as JSON to workspace: /my-swimlane-flow.tldraw
4. Agent can export to PNG/SVG if needed
User Request: "Show me all my whiteboards and let me pick one to edit"
Agent Flow:
1. Agent calls list-boards on running server
2. Agent generates gallery UI with:
- Board names and creation dates
- Thumbnail previews
- Links to view/edit each board
3. User selects a board → opens in tldraw viewer
scripts/create-board.jsProgrammatically create a .tldraw file with shapes, bindings, and text.
Usage:
node scripts/create-board.js \
--name "my-diagram" \
--title "System Architecture" \
--config config.json
Output: my-diagram.tldraw (JSON serialized TLStore snapshot)
See references/api-reference.md for detailed shape API.
scripts/deploy-server.jsDeploy a self-hosted tldraw instance using Docker or Node.js.
Usage:
node scripts/deploy-server.js \
--type docker \
--port 3000 \
--database postgresql://localhost/tldraw
Output: Docker container running tldraw server, accessible at http://localhost:3000
See references/deployment-guide.md for detailed setup.
scripts/import-board.jsImport a board JSON into a running tldraw server.
Usage:
node scripts/import-board.js \
--server http://localhost:3000 \
--board my-diagram.tldraw \
--name "My Diagram"
Output: Public URL to view/edit the board on the server.
scripts/list-boards.jsList all boards on a running server (for gallery management).
Usage:
node scripts/list-boards.js \
--server http://localhost:3000 \
--format json
Output: JSON array of board metadata.
tldraw supports a rich set of shape types. See references/shape-reference.md for complete details.
| Type | Example | Typical Use |
|------|---------|------------|
| geo | Rectangle, Ellipse, Diamond, Triangle | Flowchart nodes, diagrams |
| text | Text box | Labels, annotations, notes |
| arrow | Connector with arrowhead | Flow direction, relationships |
| frame | Container/artboard | Grouping shapes, swimlanes |
| image | Embedded image | Diagrams with images, photos |
| note | Sticky note shape | Quick notes, reminders |
| bookmark | Web link preview | References to external content |
Install Docker: https://docs.docker.com/get-docker/
Run setup script:
node scripts/deploy-server.js --type docker
Access: Open http://localhost:3000 in browser
Create boards and import:
node scripts/create-board.js --name my-diagram
node scripts/import-board.js --server http://localhost:3000 --board my-diagram.tldraw
See references/deployment-guide.md for production considerations:
tldraw documents are JSON files with the .tldraw extension. Structure:
{
"version": 15,
"document": {
"id": "doc_abc123",
"pages": {
"page_abc123": {
"id": "page_abc123",
"name": "Page 1",
"shapes": { "/* shape objects */" },
"bindings": { "/* binding objects */" }
}
},
"assets": { "/* images, videos */" },
"pageStates": { "/* UI state */" }
}
}
Key Points:
See references/api-reference.md for complete schema and type definitions.
text → richText (tldraw 3.0+)All shape props that previously accepted a plain string for label text now require a ProseMirror document object (richText). This affects both geo shapes and text shapes.
| Shape type | Old prop (< 3.0) | New prop (≥ 3.0) |
|-----------|-----------------|------------------|
| geo | props.text | props.richText |
| text | props.text | props.richText |
| arrow | props.text | props.richText |
Use the toRichText(str) helper exported by tldraw to convert plain strings:
import { toRichText } from 'tldraw'
// Single line
toRichText('Hello world')
// → { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Hello world' }] }] }
// Multi-line (one paragraph per line)
function rt(str) {
return {
type: 'doc',
content: str.split('\n').map(line => ({
type: 'paragraph',
content: line.length ? [{ type: 'text', text: line }] : [],
})),
}
}
align → textAlign for text shapestext shape horizontal alignment prop was renamed:
// Old (< 3.0)
{ type: 'text', props: { align: 'middle' } }
// New (≥ 3.0)
{ type: 'text', props: { textAlign: 'middle' } }
editor.createShapes() over loadSnapshot()store.loadSnapshot() requires exact schema sequence numbers that change with every release. Prefer the editor API for programmatic board creation — it is version-independent:
// ✅ Reliable: works across tldraw versions
<Tldraw onMount={(editor) => {
editor.createShapes([
{ id: createShapeId('box'), type: 'geo', x: 100, y: 100,
props: { w: 200, h: 100, geo: 'rectangle', richText: toRichText('Hello') } }
])
editor.zoomToFit()
}} />
// ⚠️ Fragile: schema versions must match installed tldraw version exactly
store.loadSnapshot({ schema: { schemaVersion: 2, sequences: { ... } }, store: { ... } })
Text labels placed next to arrows are independent shapes. Create them after arrows, then call editor.bringToFront() so they always render on top regardless of creation order:
editor.createShapes(labelShapes)
editor.bringToFront(labelShapes.map(s => s.id))
Use these rules for every generated board to prevent common rendering regressions:
\\n to real newlines (\n) first.toRichText(normalizedText).baseX, baseY, cardW, cardH, colGap, rowGap.(row, col).editor.bringToFront() for connector labels and badges.220px away from the right edge to avoid overlap with the tldraw style panel.60px from top/bottom edges for toolbar and zoom/minimap controls..tldraw file format vs. skill input schemaassets/example-architecture.tldraw in this skill uses a simplified input schema designed for human/agent readability — it is not a native tldraw store snapshot. Do not pass .tldraw files from this skill directly to store.loadSnapshot(). Instead, parse them and create shapes via editor.createShapes().
A working reference implementation is in test-server/src/App.jsx.
node --version (must be 18+)docker --versionlsof -i :3000 (macOS/Linux) or netstat -an | findstr 3000 (Windows).envpsql $DATABASE_URL -c "SELECT 1"curl http://localhost:3000/healthdocker logs tldraw-server (if using Docker)Upgrade and Connection headers are proxiedwscat -c wss://tldraw.example.comreferences/api-reference.md for detailed API documentation (includes tldraw 3.x richText schema)references/deployment-guide.md for production setupscripts/ for implementation examplestest-server/src/App.jsx for a working self-hosted board using the editor APIassets/example-architecture.tldraw for the simplified skill input schema (not a native tldraw snapshot)For questions or contributions, refer to research notes in session workspace: /research/tldraw-agent-skill-research.md
tools
Execute Google Cloud Platform operations using the gcloud CLI (and gsutil/bq where applicable). Use when the user wants to: authenticate with GCP, manage GCP resources, deploy applications, configure projects or IAM, view logs, run SQL/BigQuery, or interact with any GCP service from the command line. Triggers on phrases like "gcloud", "Google Cloud CLI", "deploy to GCP", "create a VM", "Cloud Run", "GKE cluster", "Cloud Storage bucket", "set GCP project", "service account", "Cloud Functions", "App Engine deploy", or any request to manage Google Cloud resources via command line.
testing
Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when user wants to stress-test a plan against their project's language and documented decisions.
development
Session-scoped git commit orchestrator that commits only current-session changes and leaves unrelated dirty worktree edits untouched. Inherits git-atomic-commit for atomic grouping and commit message execution, and git-commit-scope-constitution for scope governance and validation. Use when asked to commit this session only or isolate commits from mixed worktree state.
documentation
Compact the current conversation into a handoff document for another agent to pick up.