Pulling my boards out of Miro and into Obsidian
Miro's Export button gives you a PNG or a PDF — a photo of the data, not the data. If your boards are load-bearing maps of thought built up over years, they belong in Obsidian: editable, searchable, in a local vault. Here's how I pulled boards out of Miro via the REST API and converted them to Excalidraw and Obsidian Canvas — preserving positions, colors, fonts, curved connectors, mindmap nodes — and the lesson goes wider than Miro: every SaaS deserves an exit plan BEFORE you ever open it.
Any tool that lives in a browser is holding you hostage to its UI. Especially once you've poured time, cards, boards into it — and you realize your map of thinking is sitting on someone else's server, in a format you can't open without their page loaded.
I have a few Miro boards that long ago became load-bearing — reflections, plans, mental maps built up over years. The Export button gives you a PNG or a PDF. That isn't data, it's a photo of the data.
Why pull them out
Vendor lock-in isn't a theoretical problem. It's concrete: to download your own data through the API, you have to upgrade to the Expert tier. You're paying for permission to take back what's already yours.
I've lived by an Obsidian-first principle for a long time: anything important sits in a local vault, in plain files I can git, grep, and read ten years from now. A tool that doesn't export into that format is a tool you need to walk away from before the dependency becomes unbreakable.
Why Export doesn't save you
Miro's Export button gives you a PNG or a PDF. That's raster — the structure is gone. You can't change the text in a box. You can't drag a shape. You can't grep a label. It's a funeral card for the board, not the board itself.
Obsidian has two alternatives that live inside the vault: the Excalidraw plugin and native Canvas. Both are JSON. Both are editable. Both are searchable. Both sit in your vault next to your notes, and you can grep the text from inside Obsidian.
How you pull them out when there's no 'Export to Obsidian' button
When the UI Export doesn't give you what you need, you climb into the API. Miro has a REST API: it returns a JSON description of every shape on a board. Excalidraw and Obsidian Canvas are both open-format JSON. Between them sits a mapping.
I wrote two Python scripts. miro_to_excalidraw.py and miro_to_canvas.py. Standard library only — zero deps. Run them with a token and a board ID, you get a file, drop it into the vault.
What survived the conversion
The job wasn't just to move shapes across — it was to preserve the visual so the board reads the same as it did inside Miro.
- Positions and sizes — every shape's coordinates 1:1
- Colors with fill opacity (40% yellow stays 40% yellow)
- Borders — color, width, style (solid / dashed)
- Fonts and alignment — monospace stays monospace, center stays center
- Connectors — arrows attach at the same points Miro attached them
- Curved connectors — bow arcs instead of straight lines
- Images embedded into the file (not external URLs)
- Mindmap nodes — text pulled out through the experimental Mindmap API
- Z-ordering — overlaps don't get tangled
Where it got awkward
The Miro API has a couple of holes I had to reverse-engineer my way around.
- Curved connectors — Miro doesn't expose control points through the API. So instead of the exact original curve, I synthesize a bow — a fixed-radius arc between the two endpoints. Visually close, mathematically not identical.
- Z-index — the Miro API doesn't return a z field at all. Which shape sits on top, which sits below? Heuristic: larger area = further back. It works because in real boards the big containers usually sit underneath the smaller elements.
- Mindmap — the text of mindmap nodes isn't reachable through the regular items API. You need a separate call to the experimental Mindmap endpoint. Filled nodes become boxes, unfilled ones become plain text.
Excalidraw vs Canvas — how to pick
If you think in drawings — sketchy lines, hand-drawn feel, freeform compositions — pick Excalidraw. The Excalidraw plugin renders the file in the same aesthetic style you've seen on whiteboards.
If your boards are mostly rectangles with text connected by arrows, and you live in Obsidian as your second brain — pick Canvas. Canvas is native, opens in one click, and is wired into the vault's backlinks and tags.
I convert some boards into Excalidraw (where the visual style matters) and others into Canvas (where I want speed and the native feel). The repo supports both formats — you pick at runtime.
Summary
Python 3, standard library only. No pip install, no virtualenv, no node_modules. python3 miro_to_excalidraw.py 'BOARD_ID' 'output.excalidraw' — and the file is in your hands.
Repo: github.com/oleksiimazurenko/miro-to-obsidian. MIT license. PRs welcome — especially for edge cases I haven't hit yet.
The lesson is bigger than Miro
SaaS lock-in is a drug. The first months are free. Six months in, it's a habit. A year in, your data lives in a format only their UI can read. Two years in, you're paying for the Expert tier to extract what's already yours.
The conclusion isn't "don't use SaaS". The conclusion is that every tool you let become load-bearing needs an exit plan BEFORE you ever open it. The question "how do I get out of here when I need to" matters just as much as "how nice is the UI". A tool without plain-text export or an open API is a tool with a hidden price tag you'll pay two years from now.
Obsidian-first isn't because Obsidian is perfect. It's because plain markdown files are a format that will outlive Obsidian itself. Same way a plain Excalidraw JSON file will outlive the plugin. Same way this Python script will outlive Miro.