org-roam-project
- Overview
- Requirements
- Installation
- Quick start
- Commands
- Configuration
- How it works
- File layout
- Limitations
- License
Per-project org-roam databases via project.el.
Overview
org-roam-project extends org-roam so that each project.el-recognised project
(a Git repository, or any directory with a VCS root) can have its own isolated
notes directory and SQLite database.
When you work inside a project, the org-roam-project-* commands scope
automatically to that project's notes and database. Your personal zettelkasten
— the global org-roam-directory — is completely unaffected.
Personal zettelkasten Project notes
┌──────────────────┐ ┌──────────────────────────┐
│ ~/org-roam/ │ │ ~/code/my-project/ │
│ │ │ notes/ ← notes │
│ ~/.emacs.d/ │ │ notes/.org-roam.db ← DB │
│ org-roam.db │ └──────────────────────────┘
└──────────────────┘
▲ ▲
(not in a project) (inside a project)
Requirements
- Emacs 28.1 or later (
project.elis built in) - org-roam 2.2.0 or later
- A working org-roam setup (
org-roam-directoryalready configured)
Installation
Manual
Clone or copy org-roam-project.el somewhere on your load-path, then:
(require 'org-roam-project)
(org-roam-project-mode)
use-package
(use-package org-roam-project
:load-path "path/to/org-roam-project"
:after org-roam
:config
(org-roam-project-mode))
Quick start
- Open any file inside a
project.elproject (e.g. a Git repository). -
Initialise org-roam for that project:
M-x org-roam-project-init
This creates the notes subdirectory (
notes/by default), adds the database file to.gitignoreif one is present, and runs an initial database sync. -
Start taking notes:
M-x org-roam-project-node-find ; find or create a note M-x org-roam-project-capture ; capture a new note
-
These commands are also available from the project dispatch menu:
C-x p p ; project-switch-project n ; Roam find (org-roam-project-node-find) N ; Roam capture (org-roam-project-capture)
Commands
| Command | Description |
|---|---|
org-roam-project-init |
Initialise the current project (create dir, update .gitignore, sync DB) |
org-roam-project-node-find |
Find or create a node in the project (like org-roam-node-find) |
org-roam-project-node-insert |
Insert a link to a project node (like org-roam-node-insert) |
org-roam-project-capture |
Capture a new project note (like org-roam-capture) |
org-roam-project-buffer-toggle |
Toggle the backlinks buffer scoped to the project |
org-roam-project-db-sync |
Manually re-sync the project's org-roam database |
Configuration
Customisation variables
| Variable | Default | Description |
|---|---|---|
org-roam-project-notes-subdir |
"notes" |
Subdirectory within the project root for notes |
org-roam-project-db-filename |
".org-roam.db" |
Filename of the per-project SQLite database |
org-roam-project-auto-create |
nil |
Automatically create the notes dir if it is missing |
These can be changed globally:
(setq org-roam-project-notes-subdir "docs/notes"
org-roam-project-db-filename ".roam.db")
Per-project configuration via .dir-locals.el
All three variables are declared safe-local-variable, so they can be
overridden in a project's .dir-locals.el without Emacs prompting you:
;; ~/code/my-project/.dir-locals.el
((nil . ((org-roam-project-notes-subdir . "docs/notes")
(org-roam-project-db-filename . ".org-roam.db"))))
This lets different projects store their notes in different places without changing the global defaults.
Automatic notes directory creation
By default, commands signal an error when the project's notes directory does
not exist, reminding you to run org-roam-project-init first. If you prefer
the directory to be created automatically on first use, set:
(setq org-roam-project-auto-create t)
How it works
Context detection
Every command calls org-roam-project--context which:
- Calls
(project-current)to detect the project from the currentdefault-directory. - Reads
org-roam-project-notes-subdirandorg-roam-project-db-filename, respecting any.dir-locals.elvalues for the project root. - Returns the absolute paths to the notes directory and database file, or
nilif the notes directory does not exist.
Scoping via let-binding
Rather than patching org-roam internals, the package simply let-binds
org-roam-directory and org-roam-db-location around every call:
(let ((org-roam-directory "/path/to/project/notes")
(org-roam-db-location "/path/to/project/notes/.org-roam.db"))
(org-roam-node-find))
Because org-roam's internal connection cache is keyed by org-roam-directory,
multiple project databases can be open simultaneously with no conflicts.
Autosync on save
When org-roam-project-mode is active, an :around advice on
org-roam-db-update-file intercepts every save. If the file being saved lives
inside a project's initialised notes directory, the advice temporarily binds
the project context so the update goes to the right database. Files saved
outside any project's notes directory continue to update the global database as
normal.
project-switch-commands integration
With org-roam-project-mode enabled, two entries are added to
project-switch-commands:
(org-roam-project-node-find "Roam find" ?n)
(org-roam-project-capture "Roam capture" ?N)
These appear in the C-x p p dispatch menu and work correctly with
project-current-directory-override, so switching to a project with
C-x p p and then pressing n opens that project's notes, not the global
zettelkasten.
File layout
After running org-roam-project-init in a project, the layout looks like:
~/code/my-project/
├── .git/
├── .gitignore ← .org-roam.db appended automatically
├── src/
│ └── ...
└── notes/ ← org-roam-project-notes-subdir
├── .org-roam.db ← org-roam-project-db-filename (not committed)
├── 20260101T120000--my-first-note.org
└── 20260215T090000--another-note.org
Limitations
- No cross-project linking. Each project database is isolated. Links between a project note and your personal zettelkasten (or another project) will not appear as backlinks.
- Global capture templates. Per-project capture templates are not yet
supported. The global
org-roam-capture-templatesare used in all projects. As a workaround, you can setorg-roam-capture-templatesvia.dir-locals.el. - No graph visualisation scoping.
org-roam-ui(if you use it) is not automatically scoped to the project database.
License
GPLv3 or later. See the header of org-roam-project.el for details.