come over.
bring everything.
outl ships native importers for the three places your notes probably live. The migration is one command and reversible — your original files are never touched.
strip the
id::
poison.
outl import logseq ~/path/to/logseq-graph ~/notes what the importer does
- → strips every
id::line from your .md. Your file becomes the file you actually wrote. - → resolves
((uid))block references to[[page-name]]or((readable-id))where the destination is unambiguous. - → slugifies filenames —
My Page Title.mdbecomesmy-page-title.mdwithtitle:: My Page Titlepreserved at the top. - → seeds the
.foo.outlJSON sidecars so the CRDT op log has stable IDs from day one. Your original Logseq graph is read-only — nothing modified there.
before / after
json out,
your notes back.
Roam stores everything in a server-side JSON tree. Export from Roam (Settings → Export All → JSON), then point outl at it:
outl import roam ~/Downloads/roam-export.json ~/notes what gets translated
| roam concept | outl equivalent |
|---|---|
| Page | pages/<slug>.md with title:: property |
| Block | Markdown bullet, optionally with stable ID in sidecar |
| Daily Note | journal/<YYYY-MM-DD>.md |
[[Page]] ref | [[page]] ref (resolved to slug) |
((uid)) block ref | ((stable-id)) in markdown · ID in sidecar |
#tag | #tag (unchanged) |
Block property key:: | Block property key:: (unchanged) |
{{[[TODO]]}} | TODO with property status:: todo |
your vault is
already there.
Obsidian's vault is plain markdown. outl can read it directly — no
conversion needed. Just point
outl init at the vault and the
first run seeds sidecars for blocks that already exist.
outl init ~/ObsidianVault one file at a time
Not every migration is a whole vault. For a loose .md or .txt sitting in your Downloads
folder, right-click it and pick outl. The file lands as a page named
open-in/<file name>, linked
from that day's journal so it is reachable by something other than
search. outl imports a copy and never writes back to the original file, and opening the same one twice takes you to the page the first open
made rather than duplicating its blocks.
what carries over
- ✓
[[wiki-links]]— unchanged. - ✓
#tags— unchanged. - ✓ YAML frontmatter — preserved (outl reads it, just doesn't write delimiters).
- ✓ Daily notes plugin pattern (
YYYY-MM-DD.md) — auto-detected as journal. - ~
^block-idreferences — converted to outl block IDs on firstoutl doctorrun. - ~ Canvas (
.canvas) files — preserved as-is. Not yet rendered (on the roadmap). - · Plugin-specific syntax (Dataview queries, Templater) — left as-is. They won't break, but outl doesn't interpret them yet.
what to check.
Any block reference the importer couldn't pin down stays as
((unresolved:UID)). Run outl reconcile for an
interactive triage UI, or grep them out manually.
outl doctor walks the op
log + sidecars + .md files, reports any orphan blocks, cycle
attempts (impossible, but theoretically), or sidecar/markdown
drift. Run it once after import to baseline.
outl never touches your source. Your Logseq graph / Roam JSON /
Obsidian vault are read-only inputs. The destination directory
is a fresh outl workspace. Worst case: rm -rf ~/notes and try again with different flags.
The destination is a flat folder of .md + .outl sidecars + .outl/log.db. Drop a git init on it and commit.
Now your notes have history.
ready when
you are.
5 minutes, one command, your originals untouched.
Once imported, your notes become a second brain Claude Desktop, Cursor and ChatGPT can read over MCP.