Claude-code-plugins-plus-skills miro-hello-world
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-hello-world" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-miro-hello-world && rm -rf "$T"
manifest:
plugins/saas-packs/miro-pack/skills/miro-hello-world/SKILL.mdsource content
Miro Hello World
Overview
Minimal working example: create a board, add a sticky note, add a shape, connect them, and read the results back — all using the Miro REST API v2.
Prerequisites
- Completed
setupmiro-install-auth - Valid access token with
andboards:read
scopesboards:write
installed@mirohq/miro-api
Instructions
Step 1: Create a Board
import { MiroApi } from '@mirohq/miro-api'; const api = new MiroApi(process.env.MIRO_ACCESS_TOKEN!); async function createBoard() { // POST https://api.miro.com/v2/boards const response = await api.createBoard({ name: 'Hello World Board', description: 'Created via REST API v2', policy: { sharingPolicy: { access: 'private', // 'private' | 'view' | 'comment' | 'edit' inviteToAccountAndBoardLinkAccess: 'no_access', }, permissionsPolicy: { collaborationToolsStartAccess: 'all_editors', copyAccess: 'anyone', sharingAccess: 'owners_and_coowners', }, }, }); const boardId = response.body.id; console.log(`Board created: ${boardId}`); console.log(`View at: https://miro.com/app/board/${boardId}/`); return boardId; }
Step 2: Add a Sticky Note
async function addStickyNote(boardId: string) { // POST https://api.miro.com/v2/boards/{board_id}/sticky_notes const response = await fetch( `https://api.miro.com/v2/boards/${boardId}/sticky_notes`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.MIRO_ACCESS_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ data: { content: 'Hello from the API!', shape: 'square', // 'square' | 'rectangle' }, style: { fillColor: 'light_yellow', // light_yellow | light_green | light_blue | light_pink | etc. textAlign: 'center', // 'left' | 'center' | 'right' textAlignVertical: 'middle', }, position: { x: 0, y: 0 }, geometry: { width: 200 }, }), } ); const note = await response.json(); console.log(`Sticky note created: ${note.id} (type: ${note.type})`); return note.id; }
Step 3: Add a Shape
async function addShape(boardId: string) { // POST https://api.miro.com/v2/boards/{board_id}/shapes const response = await fetch( `https://api.miro.com/v2/boards/${boardId}/shapes`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.MIRO_ACCESS_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ data: { content: 'Next Step', shape: 'round_rectangle', // rectangle | circle | triangle | rhombus | round_rectangle | etc. }, style: { fillColor: '#4262ff', fontFamily: 'arial', fontSize: 14, textAlign: 'center', borderColor: '#1a1a2e', borderWidth: 2, borderStyle: 'normal', // 'normal' | 'dashed' | 'dotted' }, position: { x: 400, y: 0 }, geometry: { width: 200, height: 100 }, }), } ); const shape = await response.json(); console.log(`Shape created: ${shape.id} (type: ${shape.type})`); return shape.id; }
Step 4: Connect Items with a Connector
async function connectItems(boardId: string, startId: string, endId: string) { // POST https://api.miro.com/v2/boards/{board_id}/connectors const response = await fetch( `https://api.miro.com/v2/boards/${boardId}/connectors`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.MIRO_ACCESS_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ startItem: { id: startId }, endItem: { id: endId }, captions: [{ content: 'leads to' }], style: { strokeColor: '#1a1a2e', strokeWidth: 2, startStrokeCap: 'none', endStrokeCap: 'stealth', // none | stealth | arrow | filled_triangle | etc. }, }), } ); const connector = await response.json(); console.log(`Connector created: ${connector.id}`); return connector.id; }
Step 5: List All Items on the Board
async function listBoardItems(boardId: string) { // GET https://api.miro.com/v2/boards/{board_id}/items // Returns cursor-paginated results const response = await fetch( `https://api.miro.com/v2/boards/${boardId}/items?limit=50`, { headers: { 'Authorization': `Bearer ${process.env.MIRO_ACCESS_TOKEN}`, }, } ); const result = await response.json(); console.log(`Board has ${result.data.length} items:`); for (const item of result.data) { console.log(` - ${item.type}: ${item.id} (${item.data?.content ?? 'no content'})`); } // Handle pagination if (result.cursor) { console.log(`More items available. Next cursor: ${result.cursor}`); } }
Step 6: Run the Complete Flow
async function main() { const boardId = await createBoard(); const noteId = await addStickyNote(boardId); const shapeId = await addShape(boardId); await connectItems(boardId, noteId, shapeId); await listBoardItems(boardId); console.log('\nDone! Open the board in Miro to see your items.'); } main().catch(console.error);
Miro REST API v2 Item Types
| Type | Create Endpoint | Key Properties |
|---|---|---|
| | content, shape, fillColor |
| | content, shape, fillColor, borderStyle |
| | title, description, dueDate, assigneeId |
| | content, fontSize |
| | title, showContent, childrenIds |
| | url or data (base64) |
| | url |
| | url |
| | title, description, fields, status |
| | startItem, endItem, captions |
All create endpoints require
boards:write scope. All GET endpoints require boards:read.
Error Handling
| Error | HTTP Status | Cause | Solution |
|---|---|---|---|
| 404 | Invalid board_id | Verify board exists and token has access |
| 403 | Missing | Add scope in app settings |
| 400 | Bad request body | Check required fields per item type |
| 429 | Too many requests | Implement backoff (see ) |
Resources
Next Steps
Proceed to
miro-local-dev-loop for development workflow setup, or miro-core-workflow-a for board management patterns.