Extensions are an iframe-based plugin system that lets you add functionality to drawtonomy.
You can develop and test extensions using @drawtonomy/sdk and @drawtonomy/dev-server.
- Quick Start
- How Extensions are Loaded
- Developing Extensions
- Manifest
- Capabilities
- Message Protocol
- SDK Helper Functions
- Deployment
- Security
pnpm add -g @drawtonomy/dev-server
drawtonomy-dev-server
# → http://localhost:3000This downloads and serves the same app as drawtonomy.com locally.
cd extensions/ai-scene-generator
pnpm install
pnpm dev
# → http://localhost:3001http://localhost:3000/?ext=http://localhost:3001/manifest.json
The Extension panel appears on the right with the AI Scene Generator UI.
- Select API Provider (Anthropic or OpenAI)
- Select Model (e.g. Claude Sonnet 4, GPT-4o)
- Enter your API key
- Describe the scene (e.g.
A two-lane road with two cars and a pedestrian) - Click "Generate Scene"
Extensions are loaded via the ?ext=<manifestUrl> URL parameter.
# Local extension (with dev-server)
http://localhost:3000/?ext=http://localhost:3001/manifest.json
# Deployed extension (with drawtonomy.com)
https://drawtonomy.com?ext=https://my-extension.vercel.app/manifest.json
# Multiple extensions
http://localhost:3000/?ext=http://localhost:3001/manifest.json&ext=http://localhost:3002/manifest.json
Note:
drawtonomy.com(HTTPS) cannot load extensions fromlocalhost(HTTP) due to browser Private Network Access restrictions. Use@drawtonomy/dev-serverfor local development, or deploy your extension to an HTTPS host.
my-extension/
manifest.json # Extension definition (required)
index.html # Entry point (required)
src/ # Source code
mkdir my-extension && cd my-extension
pnpm init
pnpm add @drawtonomy/sdk{
"id": "my-extension",
"name": "My Extension",
"version": "1.0.0",
"description": "What this extension does",
"author": { "name": "Your Name" },
"entry": "./index.html",
"capabilities": ["shapes:write", "shapes:read", "ui:panel"]
}Extensions run inside an iframe. Use any framework (React, Vue, Svelte, vanilla JS, etc.).
With SDK (recommended):
import { ExtensionClient, createVehicle } from '@drawtonomy/sdk'
const client = new ExtensionClient('my-extension')
const init = await client.waitForInit()
console.log('Connected! Capabilities:', init.grantedCapabilities)
// Add a vehicle on button click
document.getElementById('addBtn')!.addEventListener('click', () => {
client.addShapes([createVehicle(200, 200, { templateId: 'sedan' })])
})Vanilla JS (no SDK):
<!DOCTYPE html>
<html>
<head><title>My Extension</title></head>
<body>
<button id="addBtn">Add Vehicle</button>
<script>
// 1. Send ready signal to host
window.parent.postMessage({ type: 'ext:ready', payload: { manifestId: 'my-extension' } }, '*')
// 2. Wait for init message from host
window.addEventListener('message', (event) => {
if (event.data.type === 'ext:init') {
console.log('Connected! Capabilities:', event.data.payload.grantedCapabilities)
}
})
// 3. Add shape on button click
document.getElementById('addBtn').addEventListener('click', () => {
window.parent.postMessage({
type: 'ext:shapes-add',
payload: {
shapes: [{
id: 'my-vehicle-1',
type: 'vehicle',
x: 200, y: 200,
rotation: 0, zIndex: 0,
props: {
w: 90, h: 45,
color: 'black', size: 'm',
attributes: { type: 'vehicle', subtype: 'car' },
osmId: '', templateId: 'sedan'
}
}]
}
}, '*')
})
</script>
</body>
</html># Using npx serve
npx serve . --port 3001
# Using Vite
pnpm dev --port 3001# Terminal 1: drawtonomy dev server
drawtonomy-dev-server
# Terminal 2: Your extension
pnpm dev --port 3001
# Browser
open "http://localhost:3000/?ext=http://localhost:3001/manifest.json"The manifest defines extension metadata and required permissions.
| Field | Required | Description |
|---|---|---|
id |
Yes | Unique ID (lowercase alphanumeric + hyphens) |
name |
Yes | Display name |
version |
Yes | Semver format (e.g. 1.0.0) |
description |
Yes | Description |
author |
Yes | { name: string, url?: string } |
entry |
Yes | Relative path to entry HTML |
icon |
- | Relative path to icon |
capabilities |
Yes | Array of required permissions |
minHostVersion |
- | Minimum host version |
Extensions can only use capabilities declared in their manifest. Messages for undeclared capabilities are rejected by the host.
| Capability | Description | Use Cases |
|---|---|---|
shapes:write |
Add, update, delete shapes | AI generation, import, templates |
shapes:read |
Read existing shapes | Export, analysis |
snapshot:read |
Get full snapshot | Backup, conversion |
snapshot:export |
Export scene (SVG/PNG/JPEG/PDF/EPS) | Video generation, screenshots |
viewport:read |
Get viewport info | Position-aware placement |
selection:read |
Read selection state | Process selected shapes |
ui:panel |
Display iframe UI in side panel | Custom UI |
ui:notify |
Show toast notification on host | Completion notifications |
Extensions communicate with the host via window.parent.postMessage().
Tip: Use the
ExtensionClientfrom@drawtonomy/sdkto avoid working with postMessage directly.
| Message | Capability | Description |
|---|---|---|
ext:ready |
(none) | Connection established. Host responds with ext:init |
ext:shapes-add |
shapes:write |
Add shapes |
ext:shapes-update |
shapes:write |
Update shape props |
ext:shapes-delete |
shapes:write |
Delete shapes |
ext:shapes-request |
shapes:read |
Request shape data (with filter) |
ext:snapshot-request |
snapshot:read |
Request snapshot |
ext:export-request |
snapshot:export |
Export scene (format: svg/png/jpeg/pdf/eps). Set returnData: true to receive Base64 data URI instead of downloading |
ext:viewport-request |
viewport:read |
Request viewport info |
ext:selection-request |
selection:read |
Request selection state |
ext:notify |
ui:notify |
Show toast notification |
ext:resize |
ui:panel |
Resize iframe |
| Message | Timing |
|---|---|
ext:init |
After ready, sends capability/viewport info |
ext:shapes-response |
Response to shapes-request |
ext:snapshot-response |
Response to snapshot-request |
ext:export-response |
Response to export-request. data contains Base64 data URI when returnData: true, empty string otherwise (file downloaded by host) |
ext:viewport-response |
Response to viewport-request |
ext:selection-response |
Response to selection-request |
ext:error |
On error |
The @drawtonomy/sdk package provides helpers for extension development.
High-level API wrapping postMessage communication:
import { ExtensionClient } from '@drawtonomy/sdk'
const client = new ExtensionClient('my-extension')
// Wait for initialization
const init = await client.waitForInit()
console.log('Capabilities:', init.grantedCapabilities)
// Add shapes
client.addShapes([...])
// Read shapes (returns Promise)
const shapes = await client.requestShapes({ types: ['vehicle'] })
// Get snapshot
const snapshot = await client.requestSnapshot()
// Export scene as Base64 data URI (requires snapshot:export capability)
const result = await client.requestExport('png', { returnData: true })
// result.data = "data:image/png;base64,iVBOR..."
// result.mimeType = "image/png"
// result.filename = "scene-2026-04-02T12-00-00.png"
// Export and trigger file download (default behavior)
await client.requestExport('svg')
// Get viewport
const viewport = await client.requestViewport()
// Get selection
const selection = await client.requestSelection()
// Send notification
client.notify('Done!', 'success')Helpers for creating shapes:
import {
createVehicle,
createPedestrian,
createLaneWithBoundaries,
createRectangle,
createText,
} from '@drawtonomy/sdk'
// Create a vehicle
const car = createVehicle(200, 300, { templateId: 'sedan', color: 'blue' })
// Create a lane with boundaries (handles point → linestring → lane dependencies)
const laneShapes = createLaneWithBoundaries(
[{ x: 0, y: 0 }, { x: 500, y: 0 }], // Left boundary points
[{ x: 0, y: 70 }, { x: 500, y: 70 }], // Right boundary points
{ laneOptions: { color: 'default' } }
)
// Send to host
client.addShapes([...laneShapes, car])| Type | Description | Key Props |
|---|---|---|
point |
Coordinate point | color, visible |
linestring |
Line (boundary) | pointIds[], color, strokeWidth |
lane |
Road lane | leftBoundaryId, rightBoundaryId, color |
vehicle |
Vehicle | w, h, templateId (default, sedan, bus, truck, motorcycle, bicycle) |
pedestrian |
Pedestrian | w, h, templateId (filled, walking, simple) |
rectangle |
Rectangle | w, h, color, fill |
ellipse |
Ellipse | w, h, color, fill |
arrow |
Arrow | w, h, direction |
text |
Text | text, fontSize, font |
polygon |
Polygon | pointIds[], color, fillOpacity |
traffic_light |
Traffic light | w, h |
crosswalk |
Crosswalk | pointIds[], color |
freehand |
Freehand | points[], color |
image |
Image | w, h, src |
To publish an extension, host the manifest.json and build output on HTTPS.
CORS headers are included by default — no additional configuration needed.
https://drawtonomy.com?ext=https://username.github.io/my-extension/manifest.json
Add CORS headers in vercel.json:
{
"headers": [
{
"source": "/manifest.json",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "*" }
]
}
]
}Create a _headers file:
/manifest.json
Access-Control-Allow-Origin: *
Use @drawtonomy/dev-server for local development:
# Terminal 1
drawtonomy-dev-server
# Terminal 2
pnpm dev --port 3001
# Browser
open "http://localhost:3000/?ext=http://localhost:3001/manifest.json"