Claude-code-plugins miro-core-workflow-b
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/miro-pack/skills/miro-core-workflow-b" ~/.claude/skills/jeremylongshore-claude-code-plugins-miro-core-workflow-b && rm -rf "$T"
manifest:
plugins/saas-packs/miro-pack/skills/miro-core-workflow-b/SKILL.mdsource content
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
(boards and basic items)miro-core-workflow-a - Access token with
andboards:read
scopesboards:write
Connectors
Connectors are lines that visually link two items on a board. They replaced "lines" from the v1 API.
Create a Connector
// POST https://api.miro.com/v2/boards/{board_id}/connectors const connector = await miroFetch(`/v2/boards/${boardId}/connectors`, 'POST', { startItem: { id: startItemId, position: { x: 1.0, // 0.0–1.0 relative position on item boundary y: 0.5, // 0.0 = top/left, 1.0 = bottom/right }, // or use snapTo: 'right' | 'left' | 'top' | 'bottom' | 'auto' }, endItem: { id: endItemId, snapTo: 'left', }, captions: [ { content: 'depends on', position: 0.5, // 0.0–1.0 along the connector line textAlignVertical: 'top', // 'top' | 'middle' | 'bottom' }, ], shape: 'curved', // 'straight' | 'elbowed' | 'curved' style: { color: '#1a1a2e', fontSize: 12, strokeColor: '#1a1a2e', strokeWidth: 2, strokeStyle: 'normal', // 'normal' | 'dashed' | 'dotted' startStrokeCap: 'none', // 'none' | 'stealth' | 'diamond' | 'filled_diamond' | etc. endStrokeCap: 'stealth', // arrow-style endpoint }, }); console.log(`Connector ${connector.id}: ${startItemId} → ${endItemId}`);
List All Connectors on a Board
// GET https://api.miro.com/v2/boards/{board_id}/connectors 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
// PATCH https://api.miro.com/v2/boards/{board_id}/connectors/{connector_id} await miroFetch(`/v2/boards/${boardId}/connectors/${connectorId}`, 'PATCH', { captions: [{ content: 'blocks', position: 0.5 }], style: { strokeColor: '#ff0000', endStrokeCap: 'filled_triangle' }, });
Delete a Connector
// DELETE https://api.miro.com/v2/boards/{board_id}/connectors/{connector_id} await miroFetch(`/v2/boards/${boardId}/connectors/${connectorId}`, 'DELETE');
Images
Upload Image from URL
// POST https://api.miro.com/v2/boards/{board_id}/images 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 }, // height auto-calculated from aspect ratio });
Upload Image from Base64 Data URL
import fs from 'fs'; // Read file and convert to data URL 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).
// POST https://api.miro.com/v2/boards/{board_id}/embeds const embed = await miroFetch(`/v2/boards/${boardId}/embeds`, 'POST', { data: { url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', mode: 'inline', // 'inline' | 'modal' previewUrl: '', // optional custom preview image }, 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.
// POST https://api.miro.com/v2/boards/{board_id}/app_cards 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', // 'disconnected' | 'connected' | 'disabled' 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
// PATCH https://api.miro.com/v2/boards/{board_id}/app_cards/{item_id} await miroFetch(`/v2/boards/${boardId}/app_cards/${appCardId}`, 'PATCH', { data: { status: 'connected', fields: [ { value: 'Done', fillColor: '#00c853', tooltip: 'Status' }, ], }, });
Document Items
// POST https://api.miro.com/v2/boards/{board_id}/documents 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) { // Create column frames 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', { data: { title: 'Done', format: 'custom' }, position: { x: 1000, y: 0 }, geometry: { width: 400, height: 800 }, }); // Add cards inside frames const card1 = await miroFetch(`/v2/boards/${boardId}/cards`, 'POST', { data: { title: 'Design API schema', description: 'OpenAPI 3.1 spec' }, position: { x: 0, y: -200 }, // Inside todo frame parent: { id: todoFrame.id }, }); const card2 = await miroFetch(`/v2/boards/${boardId}/cards`, 'POST', { data: { title: 'Implement auth', description: 'OAuth 2.0 flow' }, position: { x: 500, y: -200 }, parent: { id: doingFrame.id }, }); // Connect cards to show dependency await miroFetch(`/v2/boards/${boardId}/connectors`, 'POST', { startItem: { id: card1.id, snapTo: 'right' }, endItem: { id: card2.id, snapTo: 'left' }, captions: [{ content: 'blocks' }], style: { endStrokeCap: 'stealth', strokeStyle: 'dashed' }, }); }
Error Handling
| Error | Status | Cause | Solution |
|---|---|---|---|
| 404 | Start item deleted | Verify both items exist |
| 404 | End item deleted | Verify both items exist |
| 400 | URL inaccessible | Check URL is publicly reachable |
| 400 | File exceeds size limit | Resize image before upload |
| 400 | URL cannot be embedded | Check Miro's supported embed providers |
Resources
Next Steps
For common errors and troubleshooting, see
miro-common-errors.