Quickstart
1. Create a Notion integration
- Go to notion.so/profile/integrations and click + New integration.
- Pick Internal (not Public). Name it whatever (e.g. "Migrate to Obsidian"), associate it with your workspace, save.
- On the integration's Configuration page, set capabilities to the minimum the tool actually needs:
- ✅ Read content
- ❌ Update content · ❌ Insert content (we never write to Notion)
- ❌ Read comments · ❌ Insert comments (v0.2 doesn't export comments)
- 🔘 Read user information without email addresses (we only use user names in
peopleproperties)
- Copy the Internal Integration Token (
ntn_...orsecret_...).
2. Share pages with the integration
In Notion, open each top-level page or database you want to migrate. Click the ··· menu (top-right) → Connections → pick your integration. Sub-pages and database rows are reachable automatically once their root is shared.
3. Run the tool
Until the package is published to npm, clone the repo and build locally (requires Node 20+):
git clone https://github.com/oandre/notion-2-obsidian.git
cd notion-2-obsidian/app
npm install
npm run build
npm start Your browser opens at http://127.0.0.1:8765/. Paste the token, pick an output folder (absolute path), and select what to migrate. Once the package is on npm you'll be able to skip the clone with npx notion-2-obsidian@latest.
What you get
- Pages →
.mdfiles (or folders if they have sub-pages, with a sibling.mdfor the page content). - Databases → folders with one
.mdper row. Properties go into YAML frontmatter. The folder gets a sibling.mdwith a table linking each row. - Mentions, relations, link_to_page → Obsidian wikilinks (
[[Page Name]]) if the target was also extracted. Otherwise listed in_report.md. - Attachments → downloaded to
assets/, links rewritten to relative paths.
Troubleshooting
- "Nenhuma página/database compartilhada" — step 2 wasn't done. Share at least one top-level page with the integration inside Notion.
- 401/403 in the log — token is wrong or revoked. Re-issue it on the integration's Configuration page and clear the
.envnext to where you ran the tool. - A link shows up as plain text or
(link removed)— that page wasn't in your selection._report.mdlists every such case; re-run with the missing pages selected. - Workspace tree feels out of date — the picker caches the workspace structure to
<output>/.notion-2-obsidian-cache.jsonso reopens are instant. Hit the ↻ Refresh button in the picker header to re-walk the workspace.
Workspace tree cache
The first time you open the picker, the tool walks every page and database your integration can see — for big workspaces this can take a minute. The result is cached at <output>/.notion-2-obsidian-cache.json so subsequent opens are instant.
While the first walk is in progress, the UI shows live feedback: a running counter of nodes mapped so far, the current root being walked (e.g. "Mapeando 'Notas'"), and a per-root summary as each one finishes. Hit the ↻ Refresh button in the picker header to re-walk the workspace whenever you've added or moved pages in Notion.
During extraction, you'll see total counts (pages, items, databases), the current phase (rendering blocks vs downloading attachments and writing files), and a live log of each node by title.
Known limitations
- Column layouts are flattened (content kept, layout lost).
- Synced blocks render inline; Obsidian has no direct transclusion equivalent.
- Comments and version history are not exported (Notion's API doesn't expose them).
- Each run overwrites the output directory. No incremental sync.