Tutorials 25 min read

How to Clean Up a Vendor Export Into Plain Markdown

MMNMNOTE
notion-exportevernote-enexroam-jsonmarkdownpandocmigrationplain-textnote-takingpkm

To turn a vendor export — a Notion ZIP, an Evernote .enex, a Roam JSON dump — into plain Markdown you actually own, work five steps in order: rename to strip the UUIDs, re-anchor the attachments, flatten the leftover HTML, restore the links, and verify with find, rg, and wc. The export is the easy part. The cleanup is the work, and it is hours of attention, not a one-click fix.

This is the receive-side companion to a piece we already published on the push side: export before you're forced out. The drill ends when the ZIP lands on disk; this post begins where it ends. The dump is full of abc123def456 suffixes, callout blocks that arrived as raw HTML, image references that point at folders that no longer exist, and wikilinks broken by the rename. None of that is your fault. It is the shape of every vendor export — and the same five-step discipline straightens out every one of them.

What does a Notion, Evernote, or Roam export actually contain?

Notion's own help page is honest about the shape: "Any non-database Notion page can be exported as a Markdown file. Full page databases will be exports as a CSV file, with Markdown files for each subpage. Callout blocks will be exported as HTML, as there is no Markdown equivalent." 1 So a "Markdown export" is in fact a mixed bag of .md, .csv, raw HTML fragments inside .md files, and an assets/ folder of attachments — wired together by long UUID-suffixed filenames.

Evernote's .enex is one XML file per notebook, an Evernote-specific HTML dialect (ENML) wrapping note bodies, with attachments inline as base64 2. Roam emits a JSON array, one object per page; each block carries a uid, a string, and timestamps as ms since epoch 3. The formats differ; the cleanup discipline does not. The villain is the shape of a vendor-shaped export — never the vendor.

Why is the cleanup necessary at all? It's not just polish

Because the vendor's own admission is that the file export is not a round-trip. Notion's help page states it directly: "You can't instantly recreate your workspace by reuploading your exported workspace content." 4 The bytes that come out are reader-friendly text, not a restorable backup of the live database, and that gap is where every cleanup task lives. Form views, for instance, simply do not export: "At this time, you can't export a Form view of a database. Try exporting your questions and responses from Table view instead." 5

The third-party tools that consume the export say it more bluntly. Obsidian's Import from Notion help page tells you to skip the Markdown export entirely and choose HTML instead, because — verbatim — "We recommend that you do not use Notion's Markdown export as it omits important data." 6 That recommendation is not a swipe; it is a load-bearing instruction from the team that has spent the most engineering time reading these exports. Obsidian's own Notion API importer issue, which the team opened with a $5,000 bounty in 2025, names the structural reason in one sentence: "Unfortunately Notion's file-based export options don't include necessary data to recreate Databases." 7 The cleanup exists because the file format is partial by design.

Step 1: Rename to strip the UUIDs

Open any unzipped Notion export and you will see filenames like Project Plan abc123def456789abcdef0123456789a.md — a 32-character hex UUID glued onto every page and every directory. Strip them first, because every later step (image re-anchoring, link restoration, verification) gets harder while the UUIDs are still wedged into the paths.

The pattern is concrete and dull: a space, then 32 lowercase hex characters, then nothing or a folder slash. The fix is a regex rename. But before you write anything, find the files that need it, exactly as you would dry-run any bulk edit (change something across every note at once).

# 1. preview — list every file/dir whose name contains a 32-hex UUID
find ~/notion-export -regextype posix-extended -regex '.* [0-9a-f]{32}(\..+)?'

# 2. then rename. (POSIX find + a tiny shell loop works everywhere; on Linux,
#    `rename 's/ [0-9a-f]{32}//' *.md` does the same in one line.)
find ~/notion-export -depth -regextype posix-extended -regex '.* [0-9a-f]{32}(\..+)?' \
  -execdir bash -c 'mv "$1" "$(echo "$1" | sed -E "s/ [0-9a-f]{32}//")"' _ {} \;

The renamer's existence is itself evidence the problem is universal: Conner Tennery's open-source Notion-to-Obsidian-Converter (~1,000 stars on GitHub) opens with the receipt, "Windows can't handle large paths. After unzipping the Notion data I wasn't able to move the folder because Windows doesn't like long paths and Notion put a long uuid on every directory and file." 8 If you are on Windows and the unzipped folder refuses to move, the path-length limit is what hit you, and stripping the UUIDs is what fixes it.

One quirk to log before you continue: Tennery's README also notes that "Notion pages that contain parentheses or dashes in the title will have them removed by Notion while exporting your data so the file will be created without them, even though the link itself will still retain them." 9 That mismatch — file-stem without the punctuation, link with — is what breaks half the inline references in step 4. Knowing it now spares you a confused half-hour later.

Step 2: Re-anchor the attachments

Every renamed directory has just orphaned every image reference inside it. A page that said ![](img/Project%20Plan%20abc123…/diagram.png) now points at a folder that no longer exists. Move the assets next to their notes, with a stable folder layout, then rewrite the references — the seam between a Markdown note and its image is exactly the kind of brittle pointer we have written about before in markdown is portable until you add an image.

The cleanup pattern: pick one rule for where attachments live (an assets/ folder per note, or one flat attachments/ per vault — pick one and apply it everywhere), mv the files into place, then run a single search-and-replace across the Markdown bodies to rewrite the paths. The dry-run discipline is the same as step 1: search first with rg, confirm the match set, then write with sed -i.bak.

For Evernote's .enex the same step looks different because attachments are inline base64 inside the XML. A converter like evernote-backup or yarle splits one ENEX into per-note .md files and writes each attachment out as a real file — at which point this step starts again from a sane shape. Either way the destination is identical: a folder of plain Markdown files with relative paths to siblings that exist.

Step 3: Flatten the leftover HTML

Now read a few of the .md files in a text editor and you will see what Notion warned you about — <div> wrappers around callouts, raw <table> HTML where Markdown tables would have done, and inline styles that mean nothing outside the originating app. The cleanup is to convert each one with a universal text converter; the canonical tool is pandoc. Its homepage tagline is the one-line job description: "If you need to convert files from one markup format into another, pandoc is your swiss-army knife." 10

Run it per file. The conversion target is GitHub-Flavored Markdown (gfm) because that is the dialect the rest of your toolchain will agree on. The risky part — what you are giving up — is that some HTML constructs (custom callouts, colored text, embedded video iframes) have no GFM equivalent and either flatten to plain prose or vanish. That loss is structural, not user error, and is the same loss Notion's own admission named earlier 4: you preserve every word, every link, every attachment — but not the live-database semantics.

# convert every .md file's HTML island to GFM Markdown, in place, with a backup
find ~/notion-export -name "*.md" -print0 | \
  xargs -0 -I {} bash -c 'pandoc -f html -t gfm "$1" -o "$1.gfm" && mv "$1" "$1.bak" && mv "$1.gfm" "$1"' _ {}

If a vendor block is documented as having no Markdown equivalent (Notion callouts are the named case 1), accept the loss and move on — or, if a block matters to you, hand-edit that one file. The rule is to spend hours on the bulk pattern and minutes on the special cases, never the other way around.

Three classes of link will be broken after steps 1–2: cross-page wiki-style references that still carry the UUID-bearing stem of the old filename, vendor-specific block references (Roam's ((blockUid)), Notion's #abc123 deep links), and absolute paths that pointed inside the now-renamed assets folder. The portable target is the durable one — a plain [Text](relative/path.md) link, or a [[Wikilink]] if your destination app reads them — exactly the form we recommended for any note that should outlive its app in hyperlinks in your notes survive the app.

The mechanic is the same dry-run-then-write pattern as step 1: rg "abc123def456" -l0 | xargs -0 sed -i.bak 's/abc123def456//g' for the UUID remnants, repeated per pattern class. For wikilinks specifically, the per-format trap is worth its own treatment, which we covered separately in wikilinks aren't plain Markdown — and for Roam JSON, the right move is to walk the children tree per page and re-emit each ((uid)) reference as a regular link to the file you wrote that block into.

David Bieber's reverse-engineered description of the Roam JSON format is the developer-grade reference for that walk: each page is a top-level object with keys "title, children, create-time, create-email, edit-time, edit-email", and each child block carries "uid, string, edit-time, and edit-email" 11. Walk the tree per page, write one .md file per page object, flatten the bullets, and convert the block refs as you go.

Step 5: Verify with wc, find, and rg

You are done when three numbers reconcile. Count the files; count the words; grep for the failure modes you have just fixed. If any of the three surprises you, the cleanup is not yet finished.

# how many notes did we end with?
find ~/notes -name "*.md" -type f | wc -l

# rough word total across the whole vault
find ~/notes -name "*.md" -type f -print0 | xargs -0 wc -w | tail -1

# no UUIDs should remain anywhere — in filenames OR in note bodies
find ~/notes -regextype posix-extended -regex '.* [0-9a-f]{32}.*'
rg -l '\b[0-9a-f]{32}\b' ~/notes

# no leftover Notion-style HTML wrappers
rg -l '<div class="callout"|<figure class=' ~/notes

# no broken image refs (every linked image actually exists on disk)
rg -No '\!\[[^]]*\]\(([^)]+)\)' --replace '$1' ~/notes | while read -r path; do
  [ -f "$(dirname "$0")/$path" ] || echo "MISSING: $path"
done

The point of running these is not to confirm a vibe; it is to leave a paper trail. Write the before-and-after numbers (file count, word count, UUID hits) at the top of a MIGRATION.md in the vault root, and keep it. Six months later, when a citation in an old note points somewhere weird, that file is the only thing that tells you whether the link was broken in the export, by the cleanup, or by you.

How does each vendor's export break differently?

The five-step discipline is vendor-neutral, but the shape of the loss is not. The table below is sourced to each vendor's own current documentation (so it stays an evidence sheet, never a swipe).

Vendor exportWhat it emitsVendor-acknowledged lossesCleanup target
Notion — Markdown & CSV.md per page + .csv per database + sub-page .md files 1Callout blocks emit as HTML 1; Form views cannot be exported at all 5; reupload cannot recreate the workspace 4Strip UUIDs from filenames + dirs 8; flatten HTML callouts with pandoc -f html -t gfm; repair image refs the rename broke; decide what to do with CSV-only databases (treat as data, not notes)
Notion — HTMLHTML pages + assets folder + sitemapSame database gap as the Markdown export — Obsidian's importer prefers HTML over Markdown only because the file export still cannot recreate databases 7Run pandoc -f html -t gfm per page for the bulk of the content; the database-as-CSV stays CSV
Evernote — .enex (ENML XML)One .enex XML file per notebook; HTML-with-extras body + inline base64 attachments 2Tag hierarchy is lost 2; notebook stacks are lost — the docs themselves prescribe an Stack1@@@NotebookA filename workaround to recover them 2Split the .enex into per-note .md files (via yarle or evernote-backup); re-create the tag hierarchy by hand with Parent/Child slashes; re-anchor the embedded attachments to a per-note folder
Roam — JSONOne JSON array of page objects; each page = {title, children, create-time, edit-time, …}; each block = {uid, string, edit-time, …} 11View as Document formatting is lost 11; version history is lost 11; timestamps are ms since epoch 11 — needs /1000 to be human-readableFlatten the children tree into one .md per page object; convert ((uid)) block-refs into plain links; record edit-time into YAML frontmatter for posterity

A folder of plain Markdown files on your own device has none of these losses because it never had the proprietary container that produced them in the first place — no UUID suffix, no ENML, no JSON tree, nothing trapped to clean up. That is the deeper move the cleanup teaches you to keep doing afterward, not a sales line.

The five mistakes that ruin a cleanup

Almost every cleanup that goes badly traces to one of five well-known errors. Name them; the five-step order above is what defends against each.

Frequently Asked Questions

These are the questions people actually type after they have already run the export and are staring at a folder of strange filenames. Each answer follows the same five-step spine: rename, re-anchor, flatten, restore, verify — and never skip the preview before the write.

How do I clean up a Notion export?

Work through five steps in order. (1) Strip the 32-character UUIDs from every filename and directory — the receipts on this exist because Conner Tennery's converter exists for exactly this reason 8. (2) Re-anchor the attachments by moving them next to their notes and rewriting the image paths. (3) Flatten the HTML islands Notion left in the Markdown — its own help page warns that "Callout blocks will be exported as HTML, as there is no Markdown equivalent" 1 — with pandoc -f html -t gfm. (4) Restore the links the rename broke. (5) Verify with find, rg, and wc. Expect hours, not minutes.

Should I use Notion's Markdown export or its HTML export?

If you are routing through Obsidian's importer, use HTML. Its Import from Notion page is explicit: "We recommend that you do not use Notion's Markdown export as it omits important data." 6 If you are reading the files in any text editor, the Markdown export is closer to what you want but carries more HTML islands you will need to flatten. Either way the database content arrives as CSV and stays CSV — the file-based export "[doesn't] include necessary data to recreate Databases" by design 7.

What do I do with an Evernote .enex file?

Treat it as the durable bytes you own and convert from it, never back to it. Obsidian's Import from Evernote page describes the shape: "Obsidian uses Evernote's export format .enex files" and notes that "Evernote export does not keep the tag hierarchy" 2. Run a converter (yarle, evernote-backup) to split the XML into per-note .md files and write each attachment out as a real file. Then apply steps 3–5 of the cleanup. Recreate the tag hierarchy by hand with slash-separated tags (Parent/Child), because the export simply does not carry it.

How do I clean up the UUIDs in my Notion export filenames?

First list the offenders, then rename them. The pattern is a leading space followed by 32 hex characters: find ~/notion-export -regextype posix-extended -regex '.* [0-9a-f]{32}(\..+)?'. Confirm the list matches what you expect; then rename with find … -execdir bash -c 'mv …' \; or the Linux rename 's/ [0-9a-f]{32}//' *.md one-liner. On Windows, the UUIDs are also why the unzipped folder refused to move — "Windows doesn't like long paths" 8 — so the rename is what makes the rest of the cleanup possible at all.

Why is my Notion export full of HTML?

Because the format is partial. Notion's own page states the rule: "Callout blocks will be exported as HTML, as there is no Markdown equivalent." 1 The cleanup is pandoc -f html -t gfm per file, or — if the wrappers are decorative noise you do not need — a targeted sed/rg pass that strips the <div class="callout">/</div> envelope. Some constructs (custom colored text, embedded vendor blocks) have no GFM equivalent and will flatten to plain prose; that loss is structural 4.

What's the best way to move notes between apps without losing formatting?

Don't round-trip through a vendor importer that hides the conversion. Export to a format you can read with a text editor (Markdown, HTML, ENML XML, Roam JSON), clean those files with the five-step discipline above, then point the next app at the cleaned folder. The cleanup is the migration; the app on the other side is incidental. And accept up front that some loss is structural — Notion's own help page says reupload cannot recreate the workspace 4, so a "perfect" migration is not a thing the file format ever offered.

How long does a cleanup take, honestly?

For a small vault (hundreds of notes) the discipline runs in a focused afternoon. For ~10,000 notes, plan in hours-of-attention across multiple sessions, not "I'll do it tonight." Steve Yegge's writing-the-process-down rule applies — log the before-and-after counts and the regexes you ran into a MIGRATION.md in the vault root. Six months later that file is the only audit trail you will have.

A search tool should find; a converter should convert; an editor should write — and the cleanup is the discipline of running them in the right order, with backups, on bytes you already own. The pair to this discipline is the push-side drill: one drill gets the ZIP out before the clock runs out; this one turns the ZIP into a folder you actually own. Steph Ango, Obsidian's CEO, says the same idea more aphoristically: "In the fullness of time, the files you create are more important than the tools you use to create them. Apps are ephemeral, but your files have a chance to last." 13 To keep notes that already are plain files — nothing to escape, nothing to clean up — mnmnote.com stores everything as Markdown on your own device, where find, rg, sed, and pandoc already read and change them.

Footnotes

  1. "Export your content from Notion," Notion Help Center. "Any non-database Notion page can be exported as a Markdown file. Full page databases will be exports as a CSV file, with Markdown files for each subpage. Callout blocks will be exported as HTML, as there is no Markdown equivalent." https://www.notion.com/help/export-your-content (Wayback: https://web.archive.org/web/20260625205920/https://www.notion.com/help/export-your-content). Accessed 2026-06-30. 2 3 4 5 6

  2. "Import from Evernote," Obsidian Help. "Obsidian uses Evernote's export format .enex files." and "Evernote export does not keep the tag hierarchy." and "Since the export process is limited to single notebooks, the default export file lacks information about notebook stacks. However, the importer can recognize patterns in the enex file name to recreate notebook stacks as folders." https://obsidian.md/help/import/evernote (Wayback: https://web.archive.org/web/20260522192236/https://obsidian.md/help/import/evernote). Accessed 2026-06-30. 2 3 4 5

  3. David Bieber, "Roam Research's JSON Export Format," davidbieber.com, 2020-04-25. "The time (ms since epoch)." https://davidbieber.com/snippets/2020-04-25-roam-json-export/ (Wayback: https://web.archive.org/web/20260205162057/https://davidbieber.com/snippets/2020-04-25-roam-json-export/). Accessed 2026-06-30.

  4. "Export your content from Notion," Notion Help Center. "You can't instantly recreate your workspace by reuploading your exported workspace content." https://www.notion.com/help/export-your-content (Wayback: https://web.archive.org/web/20260625205920/https://www.notion.com/help/export-your-content). Accessed 2026-06-30. 2 3 4 5 6

  5. "Export your content from Notion," Notion Help Center. "At this time, you can't export a Form view of a database. Try exporting your questions and responses from Table view instead." https://www.notion.com/help/export-your-content (Wayback: https://web.archive.org/web/20260625205920/https://www.notion.com/help/export-your-content). Accessed 2026-06-30. 2

  6. "Import from Notion," Obsidian Help. "We recommend that you do not use Notion's Markdown export as it omits important data." https://obsidian.md/help/import/notion (Wayback: https://web.archive.org/web/20260628212740/https://obsidian.md/help/import/notion). Accessed 2026-06-30. 2

  7. "Notion API importer, with Databases to Bases conversion bounty - $5,000," obsidianmd/obsidian-importer issue #421, opened 2025-09-17. Issue body, Obsidian team: "Currently Importer supports converting Notion's HTML exports to Markdown via #14. Unfortunately Notion's file-based export options don't include necessary data to recreate Databases. This new importer would use the Notion API to download files progressively, and convert Databases to Bases as .base files using the YAML syntax." https://github.com/obsidianmd/obsidian-importer/issues/421 (Wayback: https://web.archive.org/web/20260628122625/https://github.com/obsidianmd/obsidian-importer/issues/421). Accessed 2026-06-30. 2 3 4

  8. Conner Tennery, "Notion-to-Obsidian-Converter," README. "Windows can't handle large paths. After unzipping the Notion data I wasn't able to move the folder because Windows doesn't like long paths and Notion put a long uuid on every directory and file." and "The script searches through every path and removes the long uuid at the end of both the directory paths and the file paths." https://github.com/connertennery/Notion-to-Obsidian-Converter (Wayback: https://web.archive.org/web/20260601110759/https://github.com/connertennery/Notion-to-Obsidian-Converter). Accessed 2026-06-30. 2 3 4 5

  9. Conner Tennery, "Notion-to-Obsidian-Converter," README. "Notion pages that contain parentheses or dashes in the title will have them removed by Notion while exporting your data so the file will be created without them, even though the link itself will still retain them." https://github.com/connertennery/Notion-to-Obsidian-Converter (Wayback: https://web.archive.org/web/20260601110759/https://github.com/connertennery/Notion-to-Obsidian-Converter). Accessed 2026-06-30.

  10. John MacFarlane, "Pandoc — a universal document converter," pandoc.org. "If you need to convert files from one markup format into another, pandoc is your swiss-army knife." https://pandoc.org/ (Wayback: https://web.archive.org/web/20260629090204/https://pandoc.org/). Accessed 2026-06-30.

  11. David Bieber, "Roam Research's JSON Export Format," davidbieber.com, 2020-04-25. Page object keys: "title, children, create-time, create-email, edit-time, edit-email"; child block universal keys: "uid, string, edit-time, and edit-email"; and "View as Documentdon't seem to get exported." https://davidbieber.com/snippets/2020-04-25-roam-json-export/ (Wayback: https://web.archive.org/web/20260205162057/https://davidbieber.com/snippets/2020-04-25-roam-json-export/). Accessed 2026-06-30. 2 3 4 5

  12. Andrew Gallant (BurntSushi), "ripgrep --replace/-r flag," ripgrep --help / man-page text (canonical flag definition). "Replaces every match with the text given when printing results. Neither this flag nor any other ripgrep flag will modify your files." https://github.com/BurntSushi/ripgrep/blob/master/crates/core/flags/defs.rs. Accessed 2026-06-30.

  13. Steph Ango, "File over app," stephango.com, 2023-07-01. "In the fullness of time, the files you create are more important than the tools you use to create them. Apps are ephemeral, but your files have a chance to last." https://stephango.com/file-over-app (Wayback: https://web.archive.org/web/20260629014022/https://stephango.com/file-over-app). Accessed 2026-06-30.