Exports Miro frames as full-detail SVGs or JSON using a headless Puppeteer browser.
If accessing a private board, a personal token is required. To get a token, log in to Miro using a regular web browser, and then copy the value of the "token" cookie from developer tools. This is the token that should be used. If the board can be accessed without an account using a public link, the token is optional.
Note: For downloading images with --download-images, authentication is typically required even for boards accessible via public links, as image resources are protected by Miro's API authentication.
You can use this tool as a command-line tool.
- Node.js >=22
- npm (built-in to Node.js), yarn, or pnpm
The CLI can be ran using npx with npx miro-export [options] (see options below). Alternatively, it's possible to install the package to the global scope with, for example, npm i -g miro-export.
Options:
-t, --token <token> Miro token
-b, --board-id <boardId> The board ID (required)
-f, --frame-names <frameNames...> The frame name(s), leave empty to export entire board
-e, --export-format <format> 'svg' or 'json' (default: 'svg')
--no-download-assets Skip downloading all assets (images, fonts, embedded SVGs)
-h, --help display help for command
The tool automatically:
- Creates a timestamped folder:
MiroBoard-{boardId}-{YYYYMMDD-HHmm}/ - Saves the export as
board.svg(ordata.jsonfor JSON format) - Downloads all assets by default (images, fonts, embedded SVGs)
# export entire board with all assets
miro-export -t XYZ -b uMoVLkx8gIc=
# Creates: ./MiroBoard-uMoVLkx8gIc-20241211-1430/board.svg
# using npx
npx miro-export -t XYZ -b uMoVLkx8gIc=
# export specific frame
miro-export -t XYZ -b uMoVLkx8gIc= -f "Frame 2"
# Creates: ./MiroBoard-uMoVLkx8gIc-20241211-1430/Frame_2/board.svg
# export multiple frames (each in separate folder)
miro-export -t XYZ -b uMoVLkx8gIc= -f "Design" "Wireframes"
# Creates: ./MiroBoard-uMoVLkx8gIc-20241211-1430/Design/board.svg
# ./MiroBoard-uMoVLkx8gIc-20241211-1430/Wireframes/board.svg
# export as JSON format
miro-export -t XYZ -b uMoVLkx8gIc= -e json
# Creates: ./MiroBoard-uMoVLkx8gIc-20241211-1430/data.json
# export WITHOUT downloading assets (SVG only, no images/fonts)
miro-export -t XYZ -b uMoVLkx8gIc= --no-download-assets
# Creates: ./MiroBoard-uMoVLkx8gIc-20241211-1430/board.svg (without assets)The tool automatically generates timestamped folders for every export:
- Format:
MiroBoard-{boardId}-{YYYYMMDD-HHmm}boardId: The cleaned board ID (alphanumeric characters only)YYYYMMDD: Year, month, and dayHHmm: Hour and minute (24-hour format)
Example: ./MiroBoard-uMoVLkx8gIc-20241211-1430/
This ensures each export is uniquely identified and prevents accidental overwrites when exporting the same board multiple times. All exports are created in the current directory.
When specifying multiple frames with the -f switch (e.g., -f "Frame 2" "Frame 3"), each frame is automatically exported to its own subfolder within the timestamped export directory. This ensures each frame is captured separately without including content between frames.
Example structure:
MiroBoard-uMoVLkx8gIc-20241211-1430/
├── Frame_2/
│ └── board.svg
└── Frame_3/
└── board.svg
The tool automatically downloads all assets by default:
- Extract asset URLs - Scans the exported SVG for all embedded asset references:
- Images: Background images, href attributes, embedded graphics
- Fonts: WOFF2, WOFF, TTF, OTF files from @font-face declarations
- Embedded SVGs: Nested SVG files and vector graphics
- Authenticate downloads - Uses the browser's authenticated session to fetch assets from Miro's API endpoints
- Auto-detect file types - Determines correct file extensions using URL analysis and magic byte detection:
- Images: .png, .jpg, .gif, .webp, .svg
- Fonts: .woff2, .woff, .ttf, .otf, .eot
- Generate proper filenames - Saves assets with resource ID-based names or URL-derived names
- Update SVG references - Replaces all remote URLs with local file paths in the exported SVG
Directory Structure:
MiroBoard-uMoVLkx8gIc-20241211-1430/
├── board.svg # Main SVG file with local asset references
├── 3458764514312409999.png # Downloaded images
├── 3458764514312410000.jpg
├── 3458764516082584645.gif
├── 3458764515101541649.woff2 # Downloaded fonts
└── 3458764515101541650.svg # Downloaded embedded SVGs
For multiple frames:
MiroBoard-uMoVLkx8gIc-20241211-1430/
├── Frame1/
│ ├── board.svg
│ ├── 123456789.png
│ ├── 987654321.woff2
│ └── 555666777.jpg
└── Frame2/
├── board.svg
├── 333444555.svg
└── 888999000.png
Features:
- Comprehensive asset detection - Finds images, fonts, and embedded SVGs using multiple regex patterns
- Smart filename generation - Uses Miro resource IDs when available, falls back to URL-based naming
- Robust file type detection - Combines URL extension analysis with magic byte detection
- Retry logic - Automatically retries failed downloads up to 3 times
- Rate limiting - Adds delays between downloads to avoid overwhelming the server
- Progress tracking - Shows detailed progress for each asset download
- Error handling - Reports successful and failed downloads with comprehensive summary
This is particularly useful for:
- Creating completely offline-viewable exports with fonts and styling preserved
- Archiving boards with all visual and typographic assets
- Ensuring assets remain accessible even if original Miro URLs change
- Converting Miro boards to self-contained documentation with proper font rendering
- Preserving embedded SVG graphics and vector elements
Requirements:
- Asset downloading is enabled by default for all exports
- Use
--no-download-assetsto skip asset downloading - Asset downloading only works with SVG exports (not JSON)
- Requires authentication token for private boards with protected assets
The JSON export format is a Miro-internal representation of all the board objects. It is not a documented format, but it is quite easy to understand. The exported format is always an array of objects that have the field type as a discriminator. Depending on the type, fields change. Some of the types have been documented as TypeScript interfaces at miro-types.ts. For example, a sticky_note object could look like this:
{
"type": "sticky_note",
"shape": "square",
"content": "<p>Test content</p>",
"style": {
"fillColor": "cyan",
"textAlign": "center",
"textAlignVertical": "middle"
},
"tagIds": [],
"id": "3458764564249021457",
"parentId": "3458764564247784511",
"origin": "center",
"relativeTo": "parent_top_left",
"createdAt": "2023-09-11T12:45:00.041Z",
"createdBy": "3458764537906310005",
"modifiedAt": "2023-09-11T12:46:01.041Z",
"modifiedBy": "3458764537906310005",
"connectorIds": [],
"x": 129.29101113436059,
"y": 201.25587788616645,
"width": 101.46000000000001,
"height": 125.12
}import { MiroBoard } from "miro-export";
await using miroBoard = new MiroBoard({
boardId: "uMoVLkx8gIc=", // required
token: "..." // optional
});
// get all board objects of type frame and with title "Frame 1"
const framesWithTitleFrame1 = await miroBoard.getBoardObjects(
{ type: "frame" }, // required (but empty object is OK too), limited field support
{ title: "Frame 1" } // optional additional filters
);
// get SVG of the first frame found above
const svgOfFrame1 = await miroBoard.getSvg([framesWithTitleFrame1[0].id]);
// if you can't use "await using" for disposal, you can also dispose manually:
// await miroBoard.dispose()
// this can also be used to close the browser at the middle of the current scopeWarning
Remember to dispose the instance to make sure the browser is closed and the process
can exit. await using (as shown above) does this automatically, but is not supported
in all environments and may not be the optimal choise in every case. Alternatively,
miroBoard.dispose() may be called at any time to dispose of the instance manually.
Types for many of the common board object types has been provided in miro-types.ts.