2026-02-18 11:35:19 +01:00
2026-02-18 11:35:19 +01:00
2026-02-18 10:26:44 +01:00
2026-02-18 10:26:44 +01:00
2026-02-18 10:26:44 +01:00
2026-02-18 10:26:44 +01:00
2026-02-18 11:35:19 +01:00
2026-02-18 11:35:19 +01:00
2026-02-18 10:26:44 +01:00
2026-02-18 10:26:44 +01:00

org-roam-project

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.el is built in)
  • org-roam 2.2.0 or later
  • A working org-roam setup (org-roam-directory already 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

  1. Open any file inside a project.el project (e.g. a Git repository).
  2. 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 .gitignore if one is present, and runs an initial database sync.

  3. 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
    
  4. 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:

  1. Calls (project-current) to detect the project from the current default-directory.
  2. Reads org-roam-project-notes-subdir and org-roam-project-db-filename, respecting any .dir-locals.el values for the project root.
  3. Returns the absolute paths to the notes directory and database file, or nil if 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-templates are used in all projects. As a workaround, you can set org-roam-capture-templates via .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.

Description
No description provided
Readme 65 KiB
Languages
Emacs Lisp 94.5%
Nix 4.2%
Makefile 1.3%