From miro-pack
Migrate Miro integrations from REST API v1 to v2 and upgrade @mirohq/miro-api SDK. Use when upgrading SDK versions, migrating v1 widget endpoints to v2 item endpoints, or handling breaking changes in the Miro platform. Trigger with phrases like "upgrade miro", "miro migration", "miro v1 to v2", "update miro SDK", "miro breaking changes".
npx claudepluginhub flight505/skill-forge --plugin miro-packThis skill is limited to using the following tools:
Guide for migrating from Miro REST API v1 to v2, upgrading the `@mirohq/miro-api` SDK, and handling the key breaking changes between versions.
Guides Next.js Cache Components and Partial Prerendering (PPR): 'use cache' directives, cacheLife(), cacheTag(), revalidateTag() for caching, invalidation, static/dynamic optimization. Auto-activates on cacheComponents: true.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Share bugs, ideas, or general feedback.
Guide for migrating from Miro REST API v1 to v2, upgrading the @mirohq/miro-api SDK, and handling the key breaking changes between versions.
| v1 Term | v2 Term | Notes |
|---|---|---|
| Widget | Item | All board elements renamed |
| Line | Connector | Lines renamed, gained captions and snapTo |
| Sticker | Sticky Note | Type value: sticky_note |
| Widget API (polymorphic) | Per-type endpoints | No more universal create endpoint |
text property | content property | In data objects |
shapeType | shape (in data) | Moved from style to data object |
startWidget / endWidget | startItem / endItem | Connector endpoints |
| Board User Connection | Board Member | Sharing/permissions model |
# v1 (DEPRECATED) → v2 (CURRENT)
POST /v1/boards/{id}/widgets → POST /v2/boards/{id}/sticky_notes
POST /v2/boards/{id}/shapes
POST /v2/boards/{id}/cards
POST /v2/boards/{id}/texts
POST /v2/boards/{id}/frames
POST /v2/boards/{id}/images
POST /v2/boards/{id}/documents
POST /v2/boards/{id}/embeds
POST /v2/boards/{id}/app_cards
GET /v1/boards/{id}/widgets → GET /v2/boards/{id}/items
GET /v1/boards/{id}/widgets/{widget_id} → GET /v2/boards/{id}/items/{item_id}
(or type-specific: /v2/boards/{id}/sticky_notes/{item_id})
POST /v1/boards/{id}/widgets (type: line) → POST /v2/boards/{id}/connectors
GET /v1/boards/{id}/widgets?type=line → GET /v2/boards/{id}/connectors
// v1: Create a sticky note (via universal widgets endpoint)
// POST /v1/boards/{id}/widgets
{
"type": "sticker",
"text": "Hello World",
"style": {
"stickerBackgroundColor": "#FFFF00"
},
"x": 100,
"y": 200,
"width": 200
}
// v2: Create a sticky note (dedicated endpoint)
// POST /v2/boards/{id}/sticky_notes
{
"data": {
"content": "Hello World", // "text" → "content"
"shape": "square" // Required in v2
},
"style": {
"fillColor": "light_yellow" // Named colors or hex
},
"position": {
"x": 100, // Nested under "position"
"y": 200
},
"geometry": {
"width": 200 // Nested under "geometry"
}
}
// v1: Connector (line)
{
"type": "line",
"startWidget": { "id": "123" },
"endWidget": { "id": "456" }
}
// v2: Connector
{
"startItem": { "id": "123", "snapTo": "right" }, // Gains snapTo/position
"endItem": { "id": "456", "snapTo": "left" },
"captions": [{ "content": "connects to" }], // New: captions
"shape": "curved", // New: shape control
"style": {
"startStrokeCap": "none",
"endStrokeCap": "stealth"
}
}
# Check current version
npm list @mirohq/miro-api
# View available versions
npm view @mirohq/miro-api versions --json | tail -5
# Create upgrade branch
git checkout -b upgrade/miro-api-v2
# Install latest
npm install @mirohq/miro-api@latest
// OLD: Some early SDK versions
import { MiroClient } from '@miro/sdk';
// CURRENT: Official @mirohq/miro-api
import { Miro, MiroApi } from '@mirohq/miro-api';
// High-level (OAuth-aware, stateful)
const miro = new Miro({ clientId, clientSecret, redirectUrl });
const userApi = await miro.as(userId);
// Low-level (stateless, pass token)
const api = new MiroApi(accessToken);
// BEFORE: Generic widget create
const widget = await client.createWidget(boardId, {
type: 'sticker',
text: 'Note content',
});
// AFTER: Type-specific item create
const stickyNote = await fetch(
`https://api.miro.com/v2/boards/${boardId}/sticky_notes`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
data: { content: 'Note content', shape: 'square' },
style: { fillColor: 'light_yellow' },
position: { x: 0, y: 0 },
}),
}
);
// v1 response: flat structure
// { id, type, text, x, y, width, style: { stickerBackgroundColor } }
// v2 response: nested structure
// {
// id, type: 'sticky_note',
// data: { content, shape },
// style: { fillColor, textAlign },
// position: { x, y, origin },
// geometry: { width },
// createdAt, modifiedAt, createdBy
// }
// Adapter for gradual migration
function adaptV2ToV1Shape(v2Item: any): any {
return {
id: v2Item.id,
type: v2Item.type === 'sticky_note' ? 'sticker' : v2Item.type,
text: v2Item.data?.content,
x: v2Item.position?.x,
y: v2Item.position?.y,
width: v2Item.geometry?.width,
};
}
# Run tests
npm test
# Verify against a test board
MIRO_TEST_BOARD_ID=your-test-board npm run test:integration
# Check for any remaining v1 patterns
grep -r "widgets\|sticker\|startWidget\|endWidget\|shapeType" src/ --include="*.ts"
// v1: offset-based pagination
// GET /v1/boards/{id}/widgets?offset=100&limit=50
// v2: cursor-based pagination
// GET /v2/boards/{id}/items?limit=50&cursor=eyJ0...
async function getAllItemsV2(boardId: string): Promise<any[]> {
const items: any[] = [];
let cursor: string | undefined;
do {
const url = new URL(`https://api.miro.com/v2/boards/${boardId}/items`);
url.searchParams.set('limit', '50');
if (cursor) url.searchParams.set('cursor', cursor);
const response = await fetch(url.toString(), {
headers: { 'Authorization': `Bearer ${token}` },
});
const result = await response.json();
items.push(...result.data);
cursor = result.cursor;
} while (cursor);
return items;
}
| Issue | Detection | Solution |
|---|---|---|
Cannot find module '@miro/sdk' | Import error | Update to @mirohq/miro-api |
widgets is not a function | SDK API change | Use per-type endpoints |
text field undefined | v2 response shape | Access via data.content |
| Connector fails | v1 line syntax | Update to startItem/endItem |
| Position wrong | Flat vs nested | Wrap in position: { x, y } |
For CI integration during upgrades, see miro-ci-integration.