↓ Skip to main content

Leaving org-roam for Obsidian

·655 words·4 mins·
Table of Contents

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:

An org-roam note with ID links, a source block and tags

The run:

Running org-roam-to-obsidian on the demo vault

And the note after:

The converted note as Obsidian Markdown

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-links leaves them alone.
  • #+filetags and #+roam_refs became YAML front matter, with a created date taken from the timestamp in the filename and a modified date 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, not 20240312091500-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-obsidian

Docker, 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:latest

Nix:

nix run github:alrayyes/org-roam-to-obsidian -- --help

From 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