I’ve kept my notes in org-roam for years, and it was wonderful. Now I’m moving them to Obsidian. This is why, and what it took to get the notes across.
Why I’m leaving #
Nothing dramatic, a pile of small things that together became annoying to deal with:
- The Emacs codebase is old. Org and org-roam are great, but Emacs carries decades of history. I use Doom Emacs because I’m not a masochist, but Doom is not always stable. Sometimes updating it is a bit too exciting.
- Multitasking could be better. Emacs does one thing at a time, and it’s the same editor I take notes in.
- Emacs Wayland support sucks. It’s recommended to use the X version, even when the Wayland version exists. I don’t like that.
- There’s a lack of integrations. Obsidian has a plugin for nearly everything. With org-roam there’s much less to plug into.
- It’s hacky to set up. A working org-roam is a pile of packages, a database, hooks, and config.
- There’s no mobile client. My notes should be on my phone too.
Getting the notes out is finicky #
Converting notes from org-roam to Obsidian brings its own set of challenges:
The database. org-roam keeps its link graph in an SQLite database. Anything that reads the notes through it needs a database that’s present, up to date and matches the files.
Internal links. org-roam links by ID, not by name. A link looks like
[[id:7c1f0a52-...][Atomic notes]], and the ID lives in a :PROPERTIES: block
of the note it points to. Obsidian links by filename: [[Atomic notes]]. So
every link has to be looked up and rewritten, and the target file has to be
named after the note’s title, or the link points at nothing.
Broken links. A note gets deleted and the links to it stay behind. org-roam doesn’t mind. Obsidian shows them as unresolved links, clickable text that goes nowhere. Out of two notes in my demo below, one link was already broken. A real vault collects them over the years.
Everything else. Properties and tags, source blocks, tables, footnotes, headings that need to sit below a title, and filenames with timestamps on the front. None of it is hard on its own. All of it has to be right.
org-roam-to-obsidian #
So I wrote vibe coded org-roam-to-obsidian,
a small Python script. It turns a directory of org-roam files into a directory
of Markdown that Obsidian can understand.
The tool skips the database. It reads the .org
files and builds its own map of ID to title from the :ID: properties, so
Emacs doesn’t have to be running and the database can be as stale as it likes.
The files are the source of truth, and they were always going to be there.
A note before:

The run:

And the note after:

What happened to it:
- ID links became wikilinks:
[[id:b4e8…][Atomic notes]]is now[[Atomic notes]]. - The dead link was unwrapped.
[[Luhmann's slip box]]became plain text, and the run told me which note it was in, so I could recreate the target if it was meant to exist.--keep-dead-linksleaves them alone. #+filetagsand#+roam_refsbecame YAML front matter, with acreateddate taken from the timestamp in the filename and amodifieddate from the file.- The source block is a fenced code block, external links are Markdown links, and the properties’ drawer is gone.
- The file is named
Zettelkasten.md, not20240312091500-zettelkasten.org, because that’s what Obsidian resolves a wikilink against. Two notes with the same title are reported, not silently overwritten.
It uses only the standard library, so there’s nothing to install before it runs.
Where to get it #
Arch Linux, from the AUR:
yay -S org-roam-to-obsidianDocker, with your notes mounted read-only:
docker run --rm \
-v ~/Documents/slip-box:/input:ro \
-v ~/obsidian-vault/imported:/output \
ghcr.io/alrayyes/org-roam-to-obsidian:latestNix:
nix run github:alrayyes/org-roam-to-obsidian -- --helpFrom source, anywhere with Python:
git clone https://github.com/alrayyes/org-roam-to-obsidian.git
cd org-roam-to-obsidian
./convert.py -i ~/org-roam -o ~/obsidian-vault/imported