migration

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.

01
from logseq

strip the
id:: poison.

command
$ 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.md becomes my-page-title.md with title:: My Page Title preserved at the top.
  • → seeds the .foo.outl JSON sidecars so the CRDT op log has stable IDs from day one. Your original Logseq graph is read-only — nothing modified there.

before / after

logseq · pages/launch.md
- ## ship 0.1.0 id:: 6624a82c-3b11-4d44-9d3f-d9c7e8f0a1b3 collapsed:: false - post #launch id:: 6624a82c-9a22-4e55-be40-eaf8d9015c2a - binaries id:: 6624a82c-7c33-4f66-cf51-f0a9eb126d3b
outl · launch.md + .launch.outl sidecar
title:: launch - ship 0.1.0 - post #launch - binaries
02
from roam research

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:

command
$ 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
03
from obsidian

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.

command
$ 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-id references — converted to outl block IDs on first outl doctor run.
  • ~ 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.
after the import

what to check.

unresolved refs

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.

integrity

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.

reversibility

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.

version control

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.