Skip to main content
RapidDev - Software Development Agency
retool-integrationsREST API Resource

How to Integrate Retool with Miro

Connect Retool to Miro by creating a REST API Resource pointing to Miro's REST API (api.miro.com/v2), authenticated with an OAuth 2.0 access token or a static app token. Pull structured data from Miro boards — sticky notes, text elements, shapes, and card items — into Retool tables for retrospective analysis, sprint planning aggregation, and turning collaborative whiteboard content into actionable structured data.

What you'll learn

  • How to create a Miro app and configure the Retool REST API Resource with app token or OAuth authentication
  • How to query the Miro API for board items including sticky notes, text, shapes, and cards
  • How to extract and structure whiteboard content from Miro boards into Retool tables for analysis
  • How to build a retrospective data aggregator that turns Miro sticky notes into structured action items
  • How to use JavaScript transformers to filter and categorize Miro board content by color, tag, or position
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate17 min read20 minutesProductivityLast updated April 2026RapidDev Engineering Team
TL;DR

Connect Retool to Miro by creating a REST API Resource pointing to Miro's REST API (api.miro.com/v2), authenticated with an OAuth 2.0 access token or a static app token. Pull structured data from Miro boards — sticky notes, text elements, shapes, and card items — into Retool tables for retrospective analysis, sprint planning aggregation, and turning collaborative whiteboard content into actionable structured data.

Quick facts about this guide
FactValue
ToolMiro
CategoryProductivity
MethodREST API Resource
DifficultyIntermediate
Time required20 minutes
Last updatedApril 2026

Why Connect Retool to Miro?

Product and engineering teams use Miro for collaborative sessions — sprint retrospectives, roadmap planning, user journey mapping, and brainstorming — but Miro's whiteboard format makes it difficult to extract insights and track action items after the session ends. Content lives in visual form on the board, but follow-up tasks, decisions, and patterns need to be in structured formats (databases, project management tools, spreadsheets) to be actionable. Connecting Miro to Retool bridges this gap.

Common use cases include a retrospective aggregator that reads sticky notes from a Miro retro board, categorizes them by column (went well, needs improvement, action items), and populates a Jira or Asana board with action items. Roadmap planning panels pull card items from Miro planning boards into Retool tables where engineering managers can sort, filter, and assign priority scores. Research teams extract affinity map clusters from Miro boards into structured analysis tables, identifying patterns in user research sessions that span dozens of boards.

Miro's REST API v2 provides access to boards, board items (sticky notes, text, shapes, frames, cards, images), team data, and tags. The API returns items with their content, geometry (position and size), and style attributes (color, font), enabling sophisticated extraction and categorization workflows that treat Miro board content as structured data.

Integration method

REST API Resource

Retool connects to Miro through a REST API Resource targeting the Miro REST API v2. Authentication uses either a static app token (for single-team access) or an OAuth 2.0 access token (for multi-user or multi-team access). All requests are proxied server-side through Retool's backend, keeping credentials off the browser. Queries target Miro's board and item endpoints to retrieve sticky notes, text elements, shapes, and cards, with JavaScript transformers extracting the content and structure from Miro's nested response format into flat arrays for Retool Tables and Charts.

Prerequisites

  • A Retool account (Cloud or self-hosted) with permission to create Resources
  • A Miro account with access to at least one Miro team and the boards you want to query — the API access level is limited by your Miro plan (Free, Starter, Business)
  • A Miro developer app created at miro.com/app/settings/user-profile/apps — from the app settings, you can obtain a static app token (for development and single-team use) or configure OAuth 2.0 (for multi-user or production use)
  • The Board ID for the Miro boards you want to query — visible in the URL when you open a Miro board: miro.com/app/board/{BOARD_ID}/
  • Basic familiarity with Miro's item types: sticky_note, text, shape, frame, card, image — the API returns different fields for each type

Step-by-step guide

1

Create a Miro app and obtain an access token

Miro's REST API requires creating a developer app in the Miro dashboard to obtain credentials. Navigate to miro.com/app/settings/user-profile/apps and click Create new app. Enter an app name (e.g., 'Retool Integration'), select the team this app will have access to, and click Create app. In the app settings, you will see two authentication options: Option 1 — Static App Token (simplest for development): In the app settings page, scroll to the 'App token' section and copy the token. This is a long-lived token that grants access to boards in the team the app is installed on. It's the fastest option for getting started but is less secure than OAuth 2.0 because it doesn't expire automatically. Option 2 — OAuth 2.0 (recommended for production): Configure the OAuth 2.0 redirect URL in the app settings (e.g., https://retool.com as a placeholder for initial setup). Miro will provide a client_id and client_secret. Implement the authorization code flow to get an access_token and refresh_token. The access_token expires after 1 hour, so production setups need token refresh handling. For initial development, use the static App Token — it works immediately without going through the OAuth flow. Note your app token; you will use it as the Bearer token in the Retool resource configuration. Also find the Board IDs for boards you want to query: open a Miro board and copy the ID from the URL (the part after /board/ and before any query parameters, which looks like uXjVN8Xxxxxxxx=). Store these in a Retool Configuration Variable or as selectable constants in your app.

Pro tip: The Miro static app token is associated with a specific team — it can only access boards within that team. If you need to access boards across multiple teams, you need separate app tokens for each team or the full OAuth 2.0 flow. For most enterprise use cases, a team-scoped app token is sufficient.

Expected result: You have a Miro app created and either a static app token or OAuth access token ready. You have copied the Board ID from at least one Miro board to use in test queries.

2

Create the Miro REST API Resource in Retool

Navigate to the Resources tab in Retool and click Add Resource. Select REST API from the resource type list. Configure the resource: - Name: 'Miro API' - Base URL: https://api.miro.com/v2 For authentication, select Bearer Token from the Auth dropdown and enter your Miro app token (or OAuth access token). Add a default header for all requests: - Key: Accept - Value: application/json Click Save Changes. Verify the resource by creating a test query: - Method: GET - Path: /boards - URL Parameters: limit: 10 Run the query. A successful response returns a list of Miro boards accessible to the token, including each board's id, name, description, and team_id. If you see a 401 error, the token is incorrect or has insufficient permissions. If the boards array is empty, the token's associated team has no boards or the app has not been installed on any team. Note the format of board IDs in the response — they contain URL-unsafe characters (= signs) that need to be URL-encoded as %3D when used in URL path segments. When using a board ID in a path (e.g., /boards/{board_id}/items), ensure the ID is properly encoded.

Pro tip: Miro board IDs contain the '=' character which must be URL-encoded as '%3D' when embedded in URL paths. In Retool's path field, use encodeURIComponent(boardIdInput.value) when dynamically building the path, or manually replace '=' with '%3D' in hardcoded board IDs.

Expected result: The Miro REST API Resource appears in the Resources list. A test GET to /boards returns a list of accessible Miro boards with their IDs and names, confirming the token is working correctly.

3

Query board items and extract sticky note content

The Miro API's /boards/{board_id}/items endpoint returns all items on a board, with optional filtering by item type. For retrospective and brainstorming boards, sticky notes are the primary content type. Create a new query using the Miro API resource: - Method: GET - Path: /boards/{{ encodeURIComponent(boardIdInput.value) }}/items - URL Parameters: - type: sticky_note (filter to only sticky notes) - limit: 50 - cursor: {{ pageState.cursor || '' }} (for pagination) The response returns an items array where each sticky_note object has: id, type, data (containing content as HTML string), style (fillColor, textColor, fontSize), geometry (x, y, width, height relative positions), and createdAt/modifiedAt timestamps. Note that Miro's item content is returned as an HTML string (e.g., '<p>Action item: Fix login bug</p>'). Your transformer needs to strip HTML tags to extract the plain text. Write a JavaScript transformer that strips HTML from content, extracts the plain text, formats the position data (x, y coordinates normalized to the board), and returns an array of cleaned sticky note objects. This cleaned array is what you bind to the Retool Table.

miroStickyNoteTransformer.js
1// JavaScript transformer for Miro sticky note items
2const items = data?.data || [];
3
4// Helper to strip HTML tags from Miro item content
5function stripHtml(html) {
6 if (!html) return '';
7 return html
8 .replace(/<br\s*\/?>/gi, '\n')
9 .replace(/<\/p>/gi, '\n')
10 .replace(/<[^>]+>/g, '')
11 .replace(/&amp;/g, '&')
12 .replace(/&lt;/g, '<')
13 .replace(/&gt;/g, '>')
14 .replace(/&quot;/g, '"')
15 .replace(/&#39;/g, "'")
16 .trim();
17}
18
19return items.map(item => ({
20 id: item.id,
21 type: item.type,
22 content: stripHtml(item.data?.content || ''),
23 color: item.style?.fillColor || '#fff9b1', // default Miro yellow
24 x_position: Math.round(item.position?.x || 0),
25 y_position: Math.round(item.position?.y || 0),
26 width: Math.round(item.geometry?.width || 200),
27 height: Math.round(item.geometry?.height || 200),
28 created_at: item.createdAt
29 ? new Date(item.createdAt).toLocaleDateString()
30 : '',
31 modified_at: item.modifiedAt
32 ? new Date(item.modifiedAt).toLocaleDateString()
33 : ''
34}));

Pro tip: Use the sticky note's color (fillColor) to infer categories in boards where teams use color-coding conventions — for example, red stickies for blockers, green for achievements, yellow for ideas. Map hex color codes to category labels in your transformer to automatically categorize notes without relying solely on position.

Expected result: A Table displays all sticky notes from the selected Miro board with their plain-text content, color, and position coordinates. The HTML is stripped cleanly from the content field. Sticky notes appear as rows with readable text content.

4

Categorize sticky notes by position to rebuild retro columns

For retrospective boards where columns are implicit (defined by position rather than explicit frames), use the x-coordinate of each sticky note to determine which column it belongs to. Miro coordinates use a center-origin system where items to the left of center have negative x values and items to the right have positive x values. Create a second transformer (or extend the first) that analyzes the position of sticky notes and assigns category labels. You need to know the approximate x-coordinate boundaries of each column — open the Miro board, select items in each column, and note their x-coordinates from the selection panel or URL parameters. Alternatively, query for frame items (type: frame) first to get the named frames on the board, then determine which sticky notes fall within each frame's bounding box based on coordinates. Frames in Miro represent sections, swimlanes, or columns — they have x, y, width, and height in the geometry object. Write a JavaScript query (no resource required) that combines the frame positions from the frames query and the sticky note positions from the items query, assigns each sticky note to the frame it falls within, and outputs the categorized list. Bind this combined output to the Retool Table with a category column showing the frame name (column label). Add a Chart component showing a count of sticky notes per category. This gives a quick visual summary of retro sentiment — how many items in each column — before drilling into the full list.

miroCategorizationQuery.js
1// JavaScript query to join sticky notes with frame categories
2// Assumes framesQuery and stickyNotesQuery are both resolved
3
4const frames = framesQuery.data || [];
5const stickyNotes = stickyNotesQuery.data || [];
6
7// Each frame has: id, content (name), x_position, y_position, width, height
8// Determine if a sticky note's center falls within a frame's bounding box
9function getCategory(note, frames) {
10 const noteCenterX = note.x_position;
11 const noteCenterY = note.y_position;
12
13 for (const frame of frames) {
14 const frameLeft = frame.x_position - frame.width / 2;
15 const frameRight = frame.x_position + frame.width / 2;
16 const frameTop = frame.y_position - frame.height / 2;
17 const frameBottom = frame.y_position + frame.height / 2;
18
19 if (noteCenterX >= frameLeft && noteCenterX <= frameRight &&
20 noteCenterY >= frameTop && noteCenterY <= frameBottom) {
21 return frame.content || 'Unnamed Section';
22 }
23 }
24 return 'Uncategorized';
25}
26
27return stickyNotes.map(note => ({
28 ...note,
29 category: getCategory(note, frames)
30}));

Pro tip: Miro uses a coordinate system where the origin (0,0) is the center of the infinite canvas. Items on the canvas can have very large positive or negative coordinates. When categorizing by position, inspect the actual coordinate values by running the items query first and checking the x/y values in Retool's State panel before writing the categorization logic.

Expected result: Sticky notes in the Table now have a 'category' column showing which frame (retro column) they belong to. The chart shows the count per category. Action items from the retro can be identified by category and sorted for follow-up.

5

Query all item types and build a board content summary

For a more comprehensive board analysis tool, query all item types simultaneously and build a content summary that categorizes the board by item type, total count, and content statistics. This is useful for boards used in user research synthesis or competitive analysis where teams mix sticky notes, text blocks, images, and cards. Create queries for each relevant item type using the Miro API resource: - One query for sticky_note items - One query for text items (type: text) - One query for card items (type: card) — cards have structured data with title, description, and optional assignee Set all three queries to run in parallel when the board ID is entered. Use a JavaScript query that combines the results: count items by type, extract text content from each type, and build a summary object. Display the summary as a set of Stat components (total sticky notes, total text items, total cards) and a comprehensive Table showing all extractable text content across item types. For card items specifically, extract the title and description separately since Miro cards have structured fields unlike the HTML-content sticky notes. This combined view gives teams a searchable archive of all whiteboard content from a specific board — useful for finding specific keywords or themes across mixed-format boards.

miroCardTransformer.js
1// JavaScript transformer for Miro card items (different structure from sticky notes)
2const cards = data?.data || [];
3
4function stripHtml(html) {
5 if (!html) return '';
6 return html.replace(/<[^>]+>/g, '').replace(/&amp;/g, '&').trim();
7}
8
9return cards.map(card => ({
10 id: card.id,
11 type: 'card',
12 title: stripHtml(card.data?.title || ''),
13 description: stripHtml(card.data?.description || ''),
14 content: [stripHtml(card.data?.title || ''), stripHtml(card.data?.description || '')]
15 .filter(Boolean).join(' — '),
16 assignee_id: card.data?.assigneeId || null,
17 due_date: card.data?.dueDate
18 ? new Date(card.data.dueDate).toLocaleDateString()
19 : '',
20 tags: (card.tagIds || []).join(', '),
21 x_position: Math.round(card.position?.x || 0),
22 y_position: Math.round(card.position?.y || 0)
23}));

Pro tip: For large Miro boards with hundreds of items, the Miro API paginates results using a cursor-based system. Check the cursor field in the response and use it in subsequent queries with the cursor parameter to fetch additional pages. Implement a 'Load More' button in Retool that fetches the next cursor page and appends results to the existing list.

Expected result: A board summary panel shows Stat components for total sticky notes, text items, and cards. A unified Table displays all item content with a type column. The search input filters across all content types to find specific keywords across the entire board.

Common use cases

Build a retrospective action item extractor

Create a Retool panel that connects to a Miro retrospective board, reads all sticky notes, categorizes them by their column (based on x-position or frame membership), and displays them in a structured Table with category (went well, improve, action items), content text, and assignee (if noted). Teams use this after each sprint retro to quickly extract and triage action items into their project management tool.

Retool Prompt

Build a retro aggregator that accepts a Miro board ID input. Query the Miro API for all sticky_note items on that board. Use the sticky note's x-position to infer which retro column it belongs to (divide the board horizontally into thirds: left=Went Well, center=Needs Improvement, right=Action Items). Display a Table grouped by category with the sticky note content. Add a 'Copy to Clipboard' button that formats the action items as a bullet list.

Copy this prompt to try it in Retool

Create a planning board data aggregator for roadmap management

Build a Retool dashboard that reads card items from Miro roadmap boards and converts them into a structured project list. Each Miro card becomes a row in a Retool Table with title, description, assigned team, and quarter/milestone (derived from which swimlane frame the card is in). Product managers use this to quickly generate structured roadmap exports from their visual Miro planning sessions.

Retool Prompt

Create a roadmap extractor for a Miro planning board. Query the Miro API for all card items and frame items on a specified board. For each card, determine which frame (swimlane or quarter) it belongs to based on parent-child relationships or positional overlap. Display a Table with card title, description, frame name (as the quarter or swimlane label), and card tag colors as priority indicators.

Copy this prompt to try it in Retool

Build a multi-board content search and analysis panel

Build a Retool panel that searches across multiple Miro boards in a team for specific content — finding all sticky notes mentioning a particular theme, keyword, or tag across all boards from the last quarter. Research teams and strategists use this to identify cross-session patterns in user research boards, competitive analysis sessions, or ideation workshops.

Retool Prompt

Create a cross-board content search panel with a team input and a keyword search. Query the Miro API for all boards in the team, then for each board fetch all sticky_note and text items. Filter items whose content contains the search keyword. Display matching items in a Table with board name, item content, and date created. Group by board name using a select component to drill into specific boards.

Copy this prompt to try it in Retool

Troubleshooting

GET /boards/{board_id}/items returns 404 Not Found even though the board exists in Miro

Cause: The board ID contains '=' characters that must be URL-encoded as '%3D' in the URL path. An unencoded '=' in the path causes the URL to be malformed, resulting in a 404 from the API.

Solution: URL-encode the board ID before using it in the path. In Retool's query path field, use: /boards/{{ encodeURIComponent(boardIdInput.value) }}/items instead of /boards/{{ boardIdInput.value }}/items. Alternatively, manually replace '=' characters with '%3D' in hardcoded board IDs.

typescript
1// URL-encode board ID in Retool query path
2// Correct:
3/boards/{{ encodeURIComponent(boardIdInput.value) }}/items
4// Incorrect:
5/boards/{{ boardIdInput.value }}/items

Sticky note content appears as raw HTML string in the Retool Table (e.g., '<p>Action item</p>')

Cause: Miro's API returns item content as HTML, not plain text. The transformer is not stripping the HTML tags before binding to the Table component.

Solution: Apply the stripHtml helper function in your transformer before returning the content field. The function should handle '<p>' tags (converting to newlines), '<br>' tags, HTML entities (&amp;, &lt;, etc.), and all other HTML elements.

typescript
1function stripHtml(html) {
2 if (!html) return '';
3 return html
4 .replace(/<br\s*\/?>/gi, '\n')
5 .replace(/<\/p>/gi, '\n')
6 .replace(/<[^>]+>/g, '')
7 .replace(/&amp;/g, '&')
8 .replace(/&lt;/g, '<')
9 .replace(/&gt;/g, '>')
10 .trim();
11}

Items query only returns the first 50 items even though the board has hundreds of sticky notes

Cause: The Miro API paginates results with a default limit of 50 items per page. The response includes a cursor value for fetching the next page, but additional pages are not automatically fetched.

Solution: Check the response for a cursor field in the top-level response object. If it exists, it means more items are available. Add a state variable in Retool to store the current cursor, and add a 'Load More' button that triggers the items query again with the cursor URL parameter set to the stored cursor value. Accumulate results across pages in a state variable rather than replacing them.

OAuth access token expires after 1 hour causing all queries to fail with 401 errors

Cause: Miro's OAuth access tokens expire after 60 minutes. The static app token does not expire but OAuth tokens do, requiring automatic refresh using the refresh_token.

Solution: Implement token refresh using Retool's Custom Auth feature on the resource, or use the static app token instead of OAuth for internal dashboards. If OAuth is required, store the refresh_token in a Retool Configuration Variable and set up a Retool Workflow that runs every 50 minutes to exchange the refresh token for a new access token, updating the MIRO_ACCESS_TOKEN configuration variable automatically.

Best practices

  • Store the Miro app token or OAuth credentials in Retool Configuration Variables marked as secret — never hardcode them in query bodies or resource headers.
  • Always URL-encode Miro board IDs using encodeURIComponent() before using them in URL paths — board IDs contain '=' characters that break URL parsing if not encoded.
  • Strip HTML from Miro item content fields before binding to Retool Table components — all Miro item content is returned as HTML strings, not plain text.
  • Use the type parameter in the /items endpoint to filter by specific item types rather than fetching all item types and filtering client-side — this reduces response size and improves query performance on large boards.
  • Implement cursor-based pagination for large boards — boards with hundreds of items will require multiple paginated requests to retrieve complete data.
  • Cache Miro board data for 10-30 minutes using Retool's query caching feature for retrospective analysis boards — the data doesn't change after the session ends, so caching prevents redundant API calls.
  • Inspect the raw Miro API response in Retool's State panel before writing transformers — item structure varies significantly between types (sticky_note, card, text, shape), and assuming a uniform structure will cause transformer errors.

Alternatives

Frequently asked questions

What is the difference between a Miro app token and an OAuth 2.0 access token for Retool integration?

A Miro app token is a static, long-lived credential associated with a specific Miro app and team. It grants access to boards within that team and is the simplest authentication method for internal Retool dashboards. An OAuth 2.0 access token is a short-lived (1-hour) token obtained through a user authorization flow, which allows the integration to act on behalf of a specific user with their permissions. For internal team dashboards where you control the Miro boards and don't need user-level permission scoping, the static app token is the simpler and recommended choice.

Can Retool create or modify Miro board content through the API?

Yes — Miro's REST API includes POST and PATCH endpoints for creating and updating board items. You can build Retool forms that create sticky notes, cards, or text items on Miro boards by sending POST requests to /boards/{board_id}/sticky_notes or /boards/{board_id}/cards. This enables two-way workflows: pull planning items from Miro for analysis in Retool, then push back approved action items or decisions to a designated Miro board section.

How do I handle Miro boards with hundreds of sticky notes in Retool?

Use Miro's cursor-based pagination — the /items endpoint returns a maximum of 50 items per request along with a cursor value for the next page. In Retool, implement pagination by storing the cursor in a state variable and adding a 'Load More' button that fetches the next page. Accumulate results across pages in a state variable array using JavaScript: state.setValue([...currentItems, ...newItems]). For very large boards (500+ items), consider using a Retool Workflow to pre-fetch and cache all items in a database rather than fetching live on each dashboard load.

Why are sticky note positions important for data extraction, and how are Miro coordinates structured?

Miro's whiteboard uses an infinite canvas with a center-origin coordinate system — the center of the canvas is (0,0), with x increasing rightward and y increasing downward. Sticky note positions determine their column or section on template boards (like retro templates where columns are defined by x-coordinate ranges). The position values in the API response are large floating-point numbers representing pixel positions on the infinite canvas. To use positions for categorization, inspect actual item coordinates by running a query and checking the position.x and position.y values in Retool's State panel.

Does Miro's API support real-time data, or is there always a delay?

The Miro REST API reflects the current saved state of boards and does not provide real-time streaming. There is typically no significant delay — the API returns the current board state at the time of the request. However, for boards where multiple users are actively editing in real-time, the API may not reflect in-progress changes until they are committed. For post-session analysis (pulling data after a retro or planning session is complete), this is not a concern. Miro does not currently offer a webhook API for real-time board change notifications.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire Retool integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.