org-mem 
- Description
- Fast info from a large number of Org file contents
- Latest
- org-mem-0.34.0.tar (.sig), 2026-Mar-03, 290 KiB
- Maintainer
- Martin Edström <meedstrom@runbox.eu>
- Website
- https://github.com/meedstrom/org-mem
- Browse ELPA's repository
- CGit or Gitweb
- All Dependencies
- el-job (.tar), llama (.tar), truename-cache (.tar)
- Badge
To install this package from Emacs, use package-install or list-packages.
Full description
A cache of metadata about the contents of all your Org files – headings, links, timestamps 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 2418 files, 8388 headings, 8624 links (3418 IDs, 4874 ID-links) in 1.95s
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 proof-of-concept.
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.
Well—Org does ship the agenda, which has tools. But 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.
1. Quick start
Example setup:
(setq org-mem-watch-dirs '("~/org" "/mnt/stuff/notes"))
(org-mem-updater-mode)
That's it – give it a couple of seconds, and now evalling (org-mem-all-entries), (org-mem-all-links) and variants should return a lot of results. See examples of how to use them at section Elisp API!
See an example in detail by typing M-x org-mem-list-example.
You do not even need to configure org-mem-watch-dirs, if Org is loaded. Org-mem will guess where to look for Org files anyway! That is controlled by the user option org-mem-do-look-everywhere (default t).
2. Two APIs
You get two different APIs to pick from, 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.
3. Data only
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) (cl-assert (org-mem-entry-p 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.
4. No Org at init
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.Or be warned about a deadline even if I don't.
That means the data must exist before Org has loaded.
- 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 and directory listings may change due to a Git operation, OS file operations, a
mvorcp -acommand 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.
5. A SQLite database, for free
Included is a drop-in for org-roam's (org-roam-db), called (org-mem-roamy-db).
In the future we may also create something that fits org-sql's DB schemata, or something custom, but we'll see!
5.1. Without org-roam installed
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])
For more tips, see section SQL API.
6. Elisp API
6.1. Example: Let org-agenda cast its net wide
You can't put 2,000 files in org-agenda-files, but most contain nothing of interest for the agenda anyway, right?
Turns out I have only about 30 files worth checking, the challenge was always knowing which files ahead-of-time. Now it's easy:
(defun my-set-agenda-files (&rest _)
(setq org-agenda-files
(cl-loop
for file in (org-mem-all-files)
;; Exclude *.org_archive or *archive/2025.org or similar
unless (string-search "archive" file)
when (seq-find (lambda (entry)
(or (org-mem-entry-active-timestamps entry)
(and (not (org-mem-entry-closed entry))
(or (org-mem-entry-todo-state entry)
(org-mem-entry-scheduled entry)
(org-mem-entry-deadline entry)))))
(org-mem-entries-in file))
collect file)))
(add-hook 'org-mem-post-full-scan-functions #'my-set-agenda-files)
(org-mem-updater-mode)
6.2. Example: Warn about dangling clocks at init
While Org can warn about dangling clocks through the org-clock-persist setting, that requires loading Org at some point during your session. Which means that if it is a matter of concern for you to forget you had a clock going, that you effectively have to put (require 'org) in your initfiles, just in case.
(Actually you're told to call (org-clock-persistence-insinuate) which does load Org for you, but you might forget that if you put it in a (use-package org :config ...) form and lazy-load it.)
Now the following is an alternative:
(defun my-warn-dangling-clock (&rest _)
(let ((entries (seq-filter #'org-mem-entry-dangling-clocks
(org-mem-all-entries))))
(when entries
(warn "Didn't clock out in files: %S"
(seq-uniq (mapcar #'org-mem-entry-file entries))))))
(add-hook 'org-mem-initial-scan-hook #'my-warn-dangling-clock)
(org-mem-updater-mode)
6.3. Force an update
If you want to force-update the cache in synchronous code, you should now (since version 0.28.0) be able to do it like this.
(org-mem-updater-update t)
To do a full reset:
(org-mem-reset t) (org-mem-await "Manually resetting..." 60)
6.4. Entries and links
We use two types of objects to help represent file contents: org-mem-entry objects and org-mem-link objects. They involve some simplifications:
- An
org-mem-linkobject corresponds either to a proper Org link, or to a citation fragment like@key1in[cite:@key1;@key2;@key3].- Check which it is with
org-mem-link-citation-p.
- Check which it is with
- The content before the first heading also 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.- Or if you're looking at the list of entries output by
(org-mem-entries-in-file FILE), the first element (thecar) is always a zeroth-level entry, no matter if it's empty or not. The rest (thecdr) are subtrees.
- Or if you're looking at the list of entries output by
Some functions behave specially if the zeroth-level entry is empty, such that the first proper Org heading is on line 1:
(org-mem-entry-at-lnum-in-file 1 FILE)then returns the entry for that heading instead of the zeroth-level entry. Ditto for(org-mem-entry-at-pos-in-file 1 FILE).That is hopefully intuitive. Opinions on API design are very welcome!
- Some predictable differences from normal entries: the zeroth-level entry obviously cannot have a TODO state, so
6.5. Full list of functions
Updated .
Getters taking no arguments
org-mem-all-entriesorg-mem-all-entries-with-active-timestampsorg-mem-all-entries-with-dangling-clockorg-mem-all-filesorg-mem-all-files-expandedorg-mem-all-files-with-active-timestampsorg-mem-all-file-truenamesorg-mem-all-id-linksorg-mem-all-id-nodesorg-mem-all-idsorg-mem-all-linksorg-mem-all-roam-reflinksorg-mem-all-roam-refsorg-mem-entry-at-point
Getters operating on an entry object
org-mem-entry-active-timestampsorg-mem-entry-active-timestamps-intorg-mem-entry-childrenorg-mem-entry-clocksorg-mem-entry-clocks-intorg-mem-entry-closedorg-mem-entry-closed-intorg-mem-entry-dangling-clocksorg-mem-entry-deadlineorg-mem-entry-deadline-intorg-mem-entry-fileorg-mem-entry-file-truenameorg-mem-entry-idorg-mem-entry-keywordsorg-mem-entry-levelorg-mem-entry-lnumorg-mem-entry-olpathorg-mem-entry-olpath-with-file-titleorg-mem-entry-olpath-with-file-title-or-basenameorg-mem-entry-olpath-with-selforg-mem-entry-olpath-with-self-with-file-titleorg-mem-entry-olpath-with-self-with-file-title-or-basenameorg-mem-entry-posorg-mem-entry-priorityorg-mem-entry-properties-inheritedorg-mem-entry-properties-local— Aliasorg-mem-entry-propertiesorg-mem-entry-propertyorg-mem-entry-property-with-inheritanceorg-mem-entry-pseudo-idorg-mem-entry-roam-aliasesorg-mem-entry-roam-refsorg-mem-entry-scheduledorg-mem-entry-scheduled-intorg-mem-entry-stats-cookiesorg-mem-entry-subtree-porg-mem-entry-tagsorg-mem-entry-tags-inheritedorg-mem-entry-tags-localorg-mem-entry-text— Always nil unlessorg-mem-do-cache-textis torg-mem-entry-titleorg-mem-entry-title-maybeorg-mem-entry-todo-stateorg-mem-id-links-to-entryorg-mem-links-in-entryorg-mem-next-entryorg-mem-previous-entryorg-mem-roam-reflinks-to-entry
Getters operating on a file name
org-mem-entries-in-file— Aliasorg-mem-file-entriesorg-mem-entries-in-filesorg-mem-entry-at-lnum-in-file— Near-aliasorg-mem-entry-at-file-lnumorg-mem-entry-at-pos-in-file— Near-aliasorg-mem-entry-at-file-posorg-mem-file-attributesorg-mem-file-char-countorg-mem-file-coding-systemorg-mem-file-id-strictorg-mem-file-id-topmostorg-mem-file-keywordsorg-mem-file-known-porg-mem-file-line-countorg-mem-file-mtimeorg-mem-file-mtime-floororg-mem-file-ptmaxorg-mem-file-sizeorg-mem-file-title-or-basenameorg-mem-file-title-strictorg-mem-file-title-topmost
Getters operating on a link object
org-mem-id-link-porg-mem-link-citation-porg-mem-link-descriptionorg-mem-link-entryorg-mem-link-entry-pseudo-idorg-mem-link-fileorg-mem-link-file-truenameorg-mem-link-nearby-idorg-mem-link-posorg-mem-link-supplementorg-mem-link-targetorg-mem-link-typeorg-mem-roam-reflink-p
Getters that return entries
org-mem-all-entriesorg-mem-all-entries-with-active-timestampsorg-mem-all-entries-with-dangling-clockorg-mem-all-id-nodesorg-mem-entries-in-file— Aliasorg-mem-file-entriesorg-mem-entries-in-filesorg-mem-entry-at-lnum-in-file— Near-aliasorg-mem-entry-at-file-lnumorg-mem-entry-at-pos-in-file— Near-aliasorg-mem-entry-at-file-posorg-mem-entry-by-idorg-mem-entry-by-pseudo-idorg-mem-entry-by-roam-reforg-mem-entry-childrenorg-mem-id-node-by-titleorg-mem-id-nodes-in-filesorg-mem-link-entry
Getters that return links
org-mem-all-id-linksorg-mem-all-linksorg-mem-all-roam-reflinksorg-mem-id-links-from-idorg-mem-id-links-into-fileorg-mem-id-links-to-entryorg-mem-id-links-to-idorg-mem-links-from-idorg-mem-links-in-entryorg-mem-links-in-fileorg-mem-links-of-typeorg-mem-links-to-roam-reforg-mem-links-to-targetorg-mem-links-with-type-and-pathorg-mem-roam-reflinks-into-fileorg-mem-roam-reflinks-into-filesorg-mem-roam-reflinks-to-entryorg-mem-roam-reflinks-to-id
Getters that return file names
org-mem-all-filesorg-mem-all-files-expandedorg-mem-all-files-with-active-timestampsorg-mem-all-file-truenamesorg-mem-entry-fileorg-mem-entry-file-truenameorg-mem-file-by-idorg-mem-file-known-porg-mem-link-fileorg-mem-link-file-truename
Misc
org-mem-all-idsorg-mem-all-roam-refsorg-mem-entry-at-pointorg-mem-id-by-title
Some API for actually activating & using org-mem as a library:
org-mem-awaitorg-mem-resetorg-mem-updater-update
7. SQL API
7.1. With org-roam installed
You can use this to end your dependence on org-roam-db-sync. Set the following to let org-mem 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's org-roam-db-query works:
(org-roam-db-query [:select * :from files :limit 10])
If it works, then various packages that depend on org-roam's DB should also work without issue. However, you'll have to be content with them pulling in org-roam even if you never turn it on.
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])
7.2. To developers
If you write a package that actually only needed to use org-roam's DB and not its other utilities like org-roam-capture, you should be able to stop requiring org-roam, and do e.g.
(defun my-db ()
(cond ((fboundp 'org-roam-db) (org-roam-db))
((fboundp 'org-mem-roamy-db) (org-mem-roamy-db))
(t (error "Enable either org-roam-db-autosync-mode or org-mem-roamy-db-mode"))
and then rewrite all your (org-roam-db-query ...) forms to:
(emacsql (my-db) ...)
In theory, anyway ;-)
Please report any issues!
7.3. Known issue
Error "attempt to write a readonly database" can happen when when you run multiple Emacs instances. Get unstuck with M-: (org-roam-db--close-all).
7.4. View what info is in the DB
Use M-x org-mem-list-db-contents.
Or the new M-x org-roam-db-explore, with exactly the same UI.
8. Tips
8.1. Encrypted files (.org.gpg .org.age)
Suppose you use age.el to keep files encrypted.
This needs extra config to tell org-mem's subprocesses how to open them. The following should do the trick:
(setq org-mem-inject-vars '(age-default-identity age-default-recipient age-program)) (setq org-mem-eval-forms '((require 'age) (age-file-enable))) (add-to-list 'org-mem-suffixes ".org.age")
9. Current limitations
9.1. Limitation: TRAMP
Files over TRAMP are excluded from org-mem's database, so as far as org-mem is concerned, it is as if they do not exist.
This limitation comes from the fact that org-mem parses your files in many parallel subprocesses that do not inherit your TRAMP connections. It is fixable in theory.
9.2. Limitation: Encrypted entries
The body text of specific entries may be encrypted by org-crypt. If so, org-mem cannot find links nor timestamps inside.
9.3. Limitation: SETUPFILE
No support yet for buffer settings from #+SETUPFILE:.
10. Future work
Old versions
| org-mem-0.33.0.tar.lz | 2026-Mar-02 | 56.4 KiB |
| org-mem-0.32.1.tar.lz | 2026-Feb-28 | 56.4 KiB |