Last active
November 18, 2025 13:25
-
-
Save mmarshall540/d722dc8b8f2ca0da9cc6a6dfcfc439d8 to your computer and use it in GitHub Desktop.
Backlinking facilities for Org-mode entries
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (define-keymap | |
| :keymap org-mode-map | |
| "C-c n b" 'my/org-backlink-search | |
| "C-c n d" 'my/org-backlink-dblock) | |
| ;; TODO Flesh this out some more, so it can be used to find other | |
| ;; kinds of links, such as to non-Org-mode files, to emails, etc. | |
| ;; Maybe use whatever is the default link-type for the current | |
| ;; location. | |
| (defun my/org-backlink-search () | |
| "Search for backlinks to the current entry. | |
| If the current entry has no ID property, use the ID property of a parent | |
| heading. If no ID can be found at all, then don't perform the search, | |
| and don't add an ID property, but inform the user. | |
| Unlike the `backlink-dblock' dynamic block, this will list entries which | |
| link to this node but do not have an ID property of their own." | |
| (interactive) | |
| (let* ((id (org-id-get (point) nil nil :inherit)) | |
| (link (concat "id:" id))) | |
| (if id | |
| (org-search-view nil link) | |
| (message | |
| "No ID property for this entry, inherited or otherwise.")))) | |
| ;; Dynamic block for backlinks | |
| (defun my/org-backlink-dblock () | |
| "Insert a dynamic block for backlinks and update it." | |
| (interactive) | |
| (unless (looking-back "\n\n" (- (point) 2)) | |
| (insert "\n")) | |
| (insert "#+BEGIN: backlinks\n#+END:") | |
| (while (looking-at "\n\n") (delete-char 1)) | |
| (previous-line) | |
| (org-dblock-update)) | |
| (defun org-dblock-write:backlinks (_params) | |
| "Find backlinks to the current node from nodes in `org-agenda-files'. | |
| Insert located backlinks at point. Only backlinks from nodes that | |
| have an ID property will be inserted. If the node where backlinks are | |
| to be inserted has no ID property, an error will issue." | |
| (let* ((id (or (org-id-get) | |
| (error (concat | |
| "Node without ID property " | |
| "cannot have backlinks!")))) | |
| (link (concat "id:" id)) | |
| (nodes (org-map-entries | |
| (lambda () | |
| (let ((sourceid (org-id-get)) | |
| (sourcetitle (org-get-heading t t t t))) | |
| (save-restriction | |
| (org-narrow-to-subtree) | |
| (when (search-forward | |
| link | |
| (save-excursion | |
| (search-forward-regexp | |
| "^#\\+BEGIN: backlinks$" nil t)) | |
| t) | |
| (cons sourceid sourcetitle))))) | |
| "ID>\"\"" 'agenda)) | |
| node) | |
| (while nodes | |
| (setq node (pop nodes)) | |
| (if node (insert "- [[id:" (car node) "][" (cdr node) "]]\n"))) | |
| (delete-line))) |
Author
Author
Aaaand of course this doesn't scale well. At least not with org-update-all-dblocks placed on after-save-hook.
If it gets too slow, I can have some code that adds a single link to a link-target's block when linking. Then, only run org-update-all-dblocks every once in a while.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This provides functionality for backlinking Org-mode entries without installing extra packages.
A command to list all entries that link to the current node (assumes that the current entry has an ID property).
A command to insert a dynamic block which can be updated with all other nodes that link to the current node (requires that both the current node and nodes that link to it have ID properties).
My reasons for doing this:
Org-roam can't work with Orgzly-revived or Orgro. That's understandable, since those are mobile apps that are not Emacs. But it means that in order to follow backlinks in those apps, the backlinks must be found in the text of the entries. The dynamic block achieves that.
There are other packages which can insert backlinks in the content of an entry, such as Org-super-links and Org-node. But a dynamic block is a built-in Org-mode feature which is easy to update automatically. And I like built-in features.
To update the dynamic block, you just enter
C-c C-cwith point on the block. Or you can use this to automatically update all dynamic blocks in an Org-mode buffer before each save.See the Info node "(org) Dynamic Blocks" for more info.