Miro Core Workflow B — Connectors, Embeds & Rich Items
Overview
Advanced item operations: connectors between items, image uploads, embedded content, app cards for custom integrations, and document items — all via the Miro REST API v2.
Prerequisites
- Completed
miro-core-workflow-a (boards and basic items)
- Access token with
boards:read and boards:write scopes
Connectors
Connectors are lines that visually link two items on a board. They replaced "lines" from the v1 API.
Create a Connector
const connector = await miroFetch(`/v2/boards/${boardId}/connectors`, 'POST', {
startItem: {
id: startItemId,
position: {
x: 1.0,
y: 0.5,
},
},
endItem: {
id: endItemId,
snapTo: 'left',
},
captions: [
{
content: 'depends on',
position: 0.5,
textAlignVertical: 'top',
},
],
shape: 'curved',
style: {
color: '#1a1a2e',
fontSize: 12,
strokeColor: '#1a1a2e',
strokeWidth: 2,
strokeStyle: 'normal',
startStrokeCap: 'none',
endStrokeCap: 'stealth',
},
});
console.log(`Connector ${connector.id}: ${startItemId} → ${endItemId}`);
List All Connectors on a Board
const connectors = await miroFetch(`/v2/boards/${boardId}/connectors?limit=50`);
for (const c of connectors.data) {
console.log(`${c.startItem.id} --[${c.captions?.[0]?.content ?? ''}]--> ${c.endItem.id}`);
}
Update a Connector
await miroFetch(`/v2/boards/${boardId}/connectors/${connectorId}`, 'PATCH', {
captions: [{ content: 'blocks', position: 0.5 }],
style: { strokeColor: '#ff0000', endStrokeCap: 'filled_triangle' },
});
Delete a Connector
await miroFetch(`/v2/boards/${boardId}/connectors/${connectorId}`, 'DELETE');
Images
Upload Image from URL
const image = await miroFetch(`/v2/boards/${boardId}/images`, 'POST', {
data: {
url: 'https://example.com/architecture-diagram.png',
title: 'System Architecture',
},
position: { x: 500, y: 0 },
geometry: { width: 400 },
});
Upload Image from Base64 Data URL
import fs from 'fs';
const imageBuffer = fs.readFileSync('diagram.png');
const base64 = imageBuffer.toString('base64');
const dataUrl = `data:image/png;base64,${base64}`;
const image = await miroFetch(`/v2/boards/${boardId}/images`, 'POST', {
data: { url: dataUrl, title: 'Local Diagram' },
position: { x: 0, y: 400 },
});
Embed Items
Embed external content (URLs rendered as previews).
const embed = await miroFetch(`/v2/boards/${boardId}/embeds`, 'POST', {
data: {
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
mode: 'inline',
previewUrl: '',
},
position: { x: -400, y: 400 },
geometry: { width: 480, height: 270 },
});
App Cards
App cards display custom data from your integration, with structured fields and status indicators.
const appCard = await miroFetch(`/v2/boards/${boardId}/app_cards`, 'POST', {
data: {
title: 'JIRA-1234: Fix login bug',
description: 'Users unable to log in after password reset',
status: 'connected',
fields: [
{ value: 'High', iconUrl: '', fillColor: '#ff0000', iconShape: 'round', tooltip: 'Priority' },
{ value: 'In Progress', fillColor: '#ffd700', iconShape: 'square', tooltip: 'Status' },
{ value: 'John Doe', tooltip: 'Assignee' },
],
},
style: { cardTheme: '#2d9bf0' },
position: { x: 600, y: 200 },
});
Update App Card Status
await miroFetch(`/v2/boards/${boardId}/app_cards/${appCardId}`, 'PATCH', {
data: {
status: 'connected',
fields: [
{ value: 'Done', fillColor: '#00c853', tooltip: 'Status' },
],
},
});
Document Items
const doc = await miroFetch(`/v2/boards/${boardId}/documents`, 'POST', {
data: {
url: 'https://example.com/spec.pdf',
title: 'Technical Specification v2',
},
position: { x: -600, y: 0 },
});
Building a Visual Workflow
Complete example: Kanban-style board with frames, cards, and connectors.
async function buildKanbanBoard(boardId: string) {
const todoFrame = await miroFetch(`/v2/boards/${boardId}/frames`, 'POST', {
data: { title: 'To Do', format: 'custom' },
position: { x: 0, y: 0 },
geometry: { width: 400, height: 800 },
});
const doingFrame = await miroFetch(`/v2/boards/${boardId}/frames`, 'POST', {
data: { title: 'In Progress', format: 'custom' },
position: { x: 500, y: 0 },
geometry: { width: 400, height: 800 },
});
const doneFrame = await miroFetch(`/v2/boards/${boardId}/frames`, 'POST', {
: { : , : },
: { : , : },
: { : , : },
});
card1 = (, , {
: { : , : },
: { : , : - },
: { : todoFrame. },
});
card2 = (, , {
: { : , : },
: { : , : - },
: { : doingFrame. },
});
(, , {
: { : card1., : },
: { : card2., : },
: [{ : }],
: { : , : },
});
}
Error Handling
| Error | Status | Cause | Solution |
|---|
connectorStartItemNotFound | 404 | Start item deleted | Verify both items exist |
connectorEndItemNotFound | 404 | End item deleted | Verify both items exist |
invalidImageUrl | 400 | URL inaccessible | Check URL is publicly reachable |
imageTooLarge | 400 | File exceeds size limit | Resize image before upload |
embedUrlNotSupported | 400 | URL cannot be embedded | Check Miro's supported embed providers |
Resources
Next Steps
For common errors and troubleshooting, see miro-common-errors.