BoardAPI Documentation
JavaScript API exposed on window.BoardAPI for programmatic control of the board editor. Designed for AI agents, browser extensions, and automation scripts.
Overview
BoardAPI provides full control over the board editor: authentication, board management, element creation/modification, viewport control, and export. All functions are synchronous unless noted with async.
localStorage via js-doc-store. No server required. Multiple users can share the same browser with isolated data.Quick Start
Open the browser console on board-editor.pages.dev and run:
// Register and create a flowchart in 10 lines await BoardAPI.register('agent@ai.com', 'password123', 'AI Agent') BoardAPI.createBoard('My Flowchart') const start = BoardAPI.addShape('roundedRect', 100, 100, {text: 'Start', fill: '#A5D6A7'}) const check = BoardAPI.addShape('diamond', 300, 80, {text: 'OK?', fill: '#FFF176'}) const done = BoardAPI.addShape('circle', 500, 100, {text: 'End', fill: '#F48FB1'}) BoardAPI.addConnector(start.id, check.id) BoardAPI.addConnector(check.id, done.id) BoardAPI.addFrame(50, 30, 600, 250, {label: 'Validation'}) BoardAPI.saveBoard()
How to Access
The API is available in any of these ways:
- Browser Console — Open DevTools (F12) and type
BoardAPI.help() - Browser Extension — Inject scripts that call
window.BoardAPI - Puppeteer / Playwright —
page.evaluate(() => BoardAPI.addShape(...)) - Claude in Chrome — Use the
javascript_toolMCP to call BoardAPI
Auth
Authentication is local (PBKDF2 + JWT stored in localStorage). Each user sees only their own boards.
register(email, password, name) async
Create a new user account and auto-login.
await BoardAPI.register('user@test.com', 'mypass123', 'Alice') // → { ok: true, email: 'user@test.com', id: '...' }
login(email, password) async
Login to an existing account. Clears the current board and updates the UI.
await BoardAPI.login('user@test.com', 'mypass123') // → { ok: true, email: 'user@test.com', id: '...' }
logout()
Sign out. Clears the canvas and session.
BoardAPI.logout() // → { ok: true }
me()
Get the current logged-in user info, or null if not logged in.
BoardAPI.me() // → { email: 'user@test.com', id: '...', roles: ['user'] }
Boards
createBoard(name)
Create a new empty board and set it as the active board.
BoardAPI.createBoard('Architecture Diagram') // → { ok: true, name: 'Architecture Diagram' }
saveBoard(name?)
Save the current board. Uses the current name if none provided.
BoardAPI.saveBoard() // save with current name BoardAPI.saveBoard('v2') // save as new name
loadBoard(name)
Load a saved board by name. Replaces the current canvas.
BoardAPI.loadBoard('Architecture Diagram') // → { ok: true, elements: 15 }
listBoards()
List all boards for the current user.
BoardAPI.listBoards() // → [{ name: 'Board 1', savedAt: '...', _id: '...' }, ...]
deleteBoard(name)
BoardAPI.deleteBoard('Old Board')
Elements
addShape(shapeType, x, y, opts?) returns {ok, id}
Add a geometric shape to the canvas.
| Param | Type | Description |
|---|---|---|
shapeType | string | One of: rectangle, roundedRect, circle, diamond, parallelogram, cylinder, hexagon, triangle, cloud |
x, y | number | World coordinates (top-left corner) |
opts.text | string | Text inside the shape |
opts.fill | string | Fill color (hex). Default: #ffffff |
opts.stroke | string | Stroke color. Default: #1a1a18 |
opts.strokeWidth | number | Stroke width. Default: 2 |
opts.w, opts.h | number | Size. Defaults vary by shape type |
opts.fontSize | number | Font size. Default: 12 |
opts.bold | boolean | Bold text. Default: false |
BoardAPI.addShape('diamond', 200, 100, { text: 'Is valid?', fill: '#FFF176', w: 140, h: 110 }) // → { ok: true, id: 'el_abc123' }
addSticky(x, y, opts?)
Add a sticky note (post-it).
| Param | Type | Description |
|---|---|---|
opts.text | string | Note text |
opts.color | number | Color index (0-11). See Colors |
opts.w | number | Width. Default: 150 |
opts.bold | boolean | Bold text |
BoardAPI.addSticky(100, 300, {text: 'Remember this', color: 4})
addFrame(x, y, w, h, opts?)
Add a frame/section to group elements visually.
| Param | Type | Description |
|---|---|---|
x, y, w, h | number | Position and size |
opts.label | string | Section label. Default: 'Section' |
opts.colorIdx | number | Color index (0-5). See Frame Colors |
BoardAPI.addFrame(50, 30, 500, 300, {label: 'Phase 1', colorIdx: 3})
addConnector(fromId, toId, opts?)
Connect two elements with a Bezier curve.
| Param | Type | Description |
|---|---|---|
fromId, toId | string | Element IDs to connect |
opts.fromSide | string | top, bottom, left, right. Default: right |
opts.toSide | string | Default: left |
opts.color | string | Stroke color. Default: #1a1a18 |
opts.dashed | boolean | Dashed line. Default: false |
opts.arrow | boolean | Arrow head. Default: true |
opts.width | number | Stroke width. Default: 2 |
BoardAPI.addConnector(start.id, end.id, { fromSide: 'bottom', toSide: 'top', color: '#c62828', dashed: true })
addLabel(x, y, opts?)
Add free-floating text.
BoardAPI.addLabel(100, 50, {text: 'Title', fontSize: 24, bold: true})
updateElement(id, props)
Update any property of an existing element.
BoardAPI.updateElement('el_abc123', {text: 'Updated!', fill: '#F48FB1'})
deleteElement(id)
Delete an element. Also removes any connectors referencing it.
BoardAPI.deleteElement('el_abc123')
listElements(filter?)
List all elements, optionally filtered by properties.
BoardAPI.listElements() // all elements BoardAPI.listElements({type: 'shape'}) // only shapes BoardAPI.listElements({type: 'connector'}) // only connectors
batch(elements[]) bulk
Add multiple elements at once. Each element must have a type field. IDs are auto-generated if missing.
BoardAPI.batch([ {type: 'shape', shapeType: 'circle', x: 100, y: 100, w: 80, h: 80, text: 'A', fill: '#A5D6A7'}, {type: 'shape', shapeType: 'rectangle', x: 250, y: 100, w: 120, h: 60, text: 'B'}, {type: 'sticky', x: 100, y: 250, text: 'Note', color: 1}, ]) // → { ok: true, added: 3, elements: [...] }
Viewport
zoomTo(scale)
Set zoom level (0.1 to 3.0). 1.0 = 100%.
BoardAPI.zoomTo(1.5) // 150%
panTo(wx, wy, scale?)
Animate the view to center on world coordinates, optionally setting zoom.
BoardAPI.panTo(400, 300, 1.0)
fitToFrame(frameName)
Zoom to fit a frame by its label name.
BoardAPI.fitToFrame('Phase 1')
resetView()
Reset to default zoom (60%) and position.
Export
exportJSON()
Get the board as a JSON object (for saving to file or sending to another system).
const data = BoardAPI.exportJSON() // → { format: 'miro-board-editor', version: 1, name: '...', elements: [...], viewport: {...} }
exportHTML()
Get the board as a standalone HTML string with built-in pan/zoom.
const html = BoardAPI.exportHTML()
Also available: BoardAPI.downloadJSON() and BoardAPI.downloadHTML() to trigger file downloads.
GitHub Sync
Sync your boards across devices using GitHub Gists as storage. No server required — works 100% client-side using the GitHub Device Flow.
Connecting GitHub
Click the "GitHub" button in the top bar. A modal will appear with:
- A link to github.com/login/device
- An 8-character code to enter on that page
After you authorize, the app detects it automatically (no redirect needed). A "Sync" button appears in the top bar.
// Flow: // 1. Click "GitHub" in topbar // 2. Modal shows code: ABCD-1234 // 3. Go to github.com/login/device // 4. Enter code, authorize // 5. App auto-detects → "Sync" button appears
Syncing Boards
Click "Sync" to push local boards to GitHub and pull remote boards. The sync is bidirectional:
| Action | What happens |
|---|---|
| Pull | Downloads boards from Gist. New boards are added locally. Existing boards are updated if the remote version is more recent. |
| Push | Uploads all local boards to the Gist (creates one if it doesn't exist). |
| Sync | Pull first, then push. Ensures both sides have the latest data. |
Cross-device workflow
// Device A: // 1. Register / Login // 2. Connect GitHub // 3. Create boards, edit, save // 4. Click "Sync" → boards pushed to Gist // Device B: // 1. Login (same email/password) // 2. Connect GitHub (same GitHub account) // 3. Click "Sync" → boards pulled from Gist // 4. Continue editing
Merge Strategy
When the same board exists both locally and remotely:
| Scenario | Result |
|---|---|
| Board only exists locally | Pushed to remote on next sync |
| Board only exists remotely | Added locally on pull |
| Both exist, remote is newer | Remote version overwrites local |
| Both exist, local is newer | Local version kept, pushed to remote |
Merge is based on the savedAt timestamp of each board. The most recent version always wins.
Security
- Gist is secret (not public) — only accessible via direct URL or API with your token
- Token scope:
gistonly — cannot access your repos, code, or other data - Token storage: saved in
localStorage(same as auth token) - No server: token never leaves your browser — all API calls go directly to github.com
- Disconnect anytime: click "GitHub (connected)" to disconnect and remove the token
API (window.GitHubSync)
The GitHub sync module is also accessible programmatically:
// Check connection status GitHubSync.isConnected() // → true/false // Push local boards to Gist await GitHubSync.push() // Pull remote boards from Gist await GitHubSync.pull() // → { added: 2, updated: 1, total: 5 } // Pull + Push (bidirectional sync) await GitHubSync.sync() // Disconnect GitHubSync.disconnect() // Get last sync time GitHubSync.getLastSync() // → '2026-04-07T...' or null // Get connected GitHub user await GitHubSync.getUser() // → { login: 'username', ... }
Storage format (Gist content)
{
"format": "board-editor-sync",
"version": 1,
"exportedAt": "2026-04-07T...",
"boards": [
{
"name": "My Flowchart",
"elements": [...],
"viewport": { "tx": 200, "ty": 200, "scale": 0.6 },
"savedAt": "2026-04-07T..."
}
]
}
Limits
| Limit | Value |
|---|---|
| Max Gist size | 10 MB (~200+ boards) |
| GitHub API rate limit | 5,000 requests/hour (authenticated) |
| Sync type | Manual (click "Sync"), not real-time |
| Collaboration | Single-user per Gist (no shared editing) |
Shape Types
| Type | Use case | Default size |
|---|---|---|
rectangle | Process / generic | 120 x 80 |
roundedRect | Activity / start-end | 120 x 80 |
circle | Start / end node | 100 x 100 |
diamond | Decision (if/else) | 120 x 100 |
parallelogram | Input / output | 140 x 80 |
cylinder | Database / storage | 100 x 120 |
hexagon | Preparation / subprocess | 130 x 100 |
triangle | Alert / note | 110 x 100 |
cloud | External service / API | 140 x 100 |
Colors
Sticky Note Colors (index 0-11)
| Index | Name | Background |
|---|---|---|
| 0 | Yellow | #FFF176 |
| 1 | Amber | #FFD54F |
| 2 | Orange | #FFCC80 |
| 3 | Orange Light | #FFF3E0 |
| 4 | Pink | #F48FB1 |
| 5 | Pink Light | #FCE4EC |
| 6 | Green | #A5D6A7 |
| 7 | Green Light | #E8F5E9 |
| 8 | Blue | #90CAF9 |
| 9 | Blue Light | #E3F2FD |
| 10 | Purple | #CE93D8 |
| 11 | Purple Light | #F3E5F5 |
Frame Colors (index 0-5)
| Index | Border |
|---|---|
| 0 | #F0997B Coral |
| 1 | #CE93D8 Purple |
| 2 | #90CAF9 Blue |
| 3 | #A5D6A7 Green |
| 4 | #FFD54F Amber |
| 5 | #FFCC80 Orange |
Data Model
Each board is stored as a document with an elements[] array. Each element has:
{
id: 'el_abc123', // unique ID
type: 'shape', // sticky | shape | frame | connector | label | draw
x: 100, y: 100, // world position
w: 120, h: 80, // size
// ... type-specific properties
}
Board Editor — Built with vanilla JS, zero dependencies. Powered by js-doc-store.