A cache of metadata about the structure of all your Org files – headings, links and so on.
Builds quickly, so that there is no need to persist data across sessions. My M-x org-mem-reset:
Org-mem saw 3397 ID-nodes and 4845 ID-links in 2394 files, 8834 subtrees, 5560 links in 1.37s (+ 0.16s making SQLite DB)
This library came from asking myself “what could I move out of org-node, that’d make sense in core?” Maybe a proposal for upstream, or at least a PoC.
Many notetaking packages now reinvent the wheel, when it comes to keeping track of some or many files and what may be in them.
Example: org-roam’s DB, org-node’s hash tables, and other packages just re-run grep all the time, which still leads to writing elisp to cross-reference the results.
And they must do this, because Org ships no tool to query across lots of files. You know what happens if you put 2,000 files into org-agenda-files! It needs to open each file in real-time to check anything in them, so everyday commands grind to a halt, or even crash: many OSes have a cap of 1,024 simultaneous file handles.
Example setup:
(setq org-mem-watch-dirs '("~/org/" "/mnt/stuff/notes/"))
(setq org-mem-do-sync-with-org-id t)
(org-mem-updater-mode)That’s it – now evalling (org-mem-all-entries), (org-mem-all-links) and variants should return a lot of results. Check the many defuns in org-mem.el for what you can do with that!
Two different APIs to access the same data.
- Emacs Lisp
- SQL
Why two? It’s free. When the data has been gathered anyway, there is no reason to only insert it into a SQLite db, nor only put it in a hash table.
Famously, org-roam uses a SQLite DB. My package org-node used simple hash tables. Now you get both, without having to install either.
A design choice: Org-mem only delivers data. It could easily ship conveniences like, let’s call it a function “org-mem-goto”:
(defun org-mem-goto (entry)
(find-file (org-mem-entry-file entry))
(goto-char (org-mem-entry-pos entry))but in my experience, that will spiral into dozens of lines over time, to handle a variety of edge cases. Since you may prefer to handle edge cases different than I do, or have different needs, it ceases to be universally applicable.
So, it is up to you to write your own “goto” function, and all else to do with user interaction.
A design choice: Org-mem does not use Org code to analyze your files, but a custom, more dumb parser. That’s for three reasons:
- Fast init. Since I want Emacs init to be fast, it’s not allowed to load Org. Yet, I want to be able to use a command like
org-node-findto browse my Org stuff immediately after init.That means the data must exist before Org has loaded.
- Future milestone: I want to be told at init if there’s a dangling Org clock or missed deadline somewhere.
- Robustness. Many users heavily customize Org, so no surprise that it sometimes breaks. In my experience, it’s very nice then to have an alternative way to browse, that does not depend on a functional Org setup.
- Fast rebuild. As they say, there are two hard things in computer science: cache invalidation and naming things.
Org-mem must update its cache as the user saves, renames and deletes files. Not difficult, until you realize that files may change due to a Git operation, OS file operations, a
rmcommand on the terminal, edits by another Emacs instance, or remote edits by Logseq.A robust approach to cache invalidation is to avoid trying: ensure that a full rebuild is fast enough that you can just do that instead.
In fact,
org-mem-updater-modedoes a bit of both, because it is still important that saving a file does not lag; it does its best to update only the necessary tables on save, and an idle timer triggers a full reset every now and then.
Included is a drop-in for org-roam’s (org-roam-db), called (org-mem-roamy-db).
In the future we may also do a drop-in for org-sql’s DB, or something custom, but we’ll see!
Activating the mode creates an in-memory database by default.
(org-mem-roamy-db-mode)Test that it works:
(emacsql (org-mem-roamy-db) [:select * :from files :limit 10])You can use this to end your dependence on org-roam-db-sync. Set the following to overwrite the “org-roam.db” file.
(setq org-roam-db-update-on-save nil)
(setq org-mem-roamy-do-overwrite-real-db t)
(org-mem-roamy-db-mode)Now, you have a new, all-fake org-roam.db! Test that org-roam-db-query works:
(org-roam-db-query [:select * :from files :limit 10])N/B: because (equal (org-roam-db) (org-mem-roamy-db)), the above is equivalent to these expressions:
(emacsql (org-roam-db) [:select * :from files :limit 10])
(emacsql (org-mem-roamy-db) [:select * :from files :limit 10])A known issue when when you use multiple Emacsen: “attempt to write a readonly database”. Get unstuck with M-: (org-roam-db--close-all).
Use the command M-x org-mem-list-db-contents.
We use two types of objects to help represent file contents: org-mem-entry objects and org-mem-link objects. They involve some simplifications:
- The content before the first heading counts as an entry with heading level zero.
- Some predictable differences from normal entries: the zeroth-level entry obviously cannot have a TODO state, so
org-mem-entry-todo-statealways returns nil, and so on. - Check with
org-mem-entry-subtree-p.
- Some predictable differences from normal entries: the zeroth-level entry obviously cannot have a TODO state, so
- An
org-mem-linkobject corresponds either to a valid Org link, or to a citation fragment.- Check with
org-mem-link-citation-p.
- Check with
TODO: Some get-started examples.
As of [2025-05-15 Thu 12:04], the full list of functions:
org-mem-all-entriesorg-mem-all-filesorg-mem-all-id-linksorg-mem-all-id-nodesorg-mem-all-idsorg-mem-all-linksorg-mem-entries-in-fileorg-mem-entries-in-filesorg-mem-entry-at-file-lnumorg-mem-entry-at-file-posorg-mem-entry-at-lnum-in-fileorg-mem-entry-at-pos-in-fileorg-mem-entry-by-idorg-mem-entry-closedorg-mem-entry-crumbsorg-mem-entry-deadlineorg-mem-entry-fileorg-mem-entry-idorg-mem-entry-levelorg-mem-entry-lnumorg-mem-entry-olpath-with-self-with-titleorg-mem-entry-olpath-with-selforg-mem-entry-olpath-with-title-with-selforg-mem-entry-olpath-with-titleorg-mem-entry-olpathorg-mem-entry-posorg-mem-entry-priorityorg-mem-entry-propertiesorg-mem-entry-propertyorg-mem-entry-scheduledorg-mem-entry-subtree-porg-mem-entry-tags-inheritedorg-mem-entry-tags-localorg-mem-entry-tagsorg-mem-entry-that-contains-linkorg-mem-entry-titleorg-mem-entry-todo-stateorg-mem-file-attributesorg-mem-file-by-idorg-mem-file-entriesorg-mem-file-id-strictorg-mem-file-id-topmostorg-mem-file-line-countorg-mem-file-mtime-intorg-mem-file-mtimeorg-mem-file-ptmaxorg-mem-file-sizeorg-mem-file-title-or-basenameorg-mem-file-title-strictorg-mem-file-title-topmostorg-mem-id-by-titleorg-mem-id-links-from-idorg-mem-id-links-to-entryorg-mem-id-links-to-idorg-mem-id-node-by-titleorg-mem-id-nodes-in-filesorg-mem-link-citation-porg-mem-link-descriptionorg-mem-link-fileorg-mem-link-nearby-idorg-mem-link-posorg-mem-link-targetorg-mem-link-typeorg-mem-links-from-idorg-mem-links-in-entryorg-mem-links-in-fileorg-mem-links-of-typeorg-mem-links-to-entryorg-mem-links-to-fileorg-mem-links-with-type-and-pathorg-mem-next-entryorg-mem-previous-entry