Agent Skills: Miro Core Workflow B — Connectors, Embeds & Rich Items

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/miro-core-workflow-b

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/miro-pack/skills/miro-core-workflow-b

Skill Files

Browse the full folder contents for miro-core-workflow-b.

Download Skill

Loading file tree…

plugins/saas-packs/miro-pack/skills/miro-core-workflow-b/SKILL.md

Skill Metadata

Name
miro-core-workflow-b
Description
|

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

// 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 | |-------|--------|-------|----------| | 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.