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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Aaaand of course this doesn't scale well. At least not with
org-update-all-dblocksplaced onafter-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-dblocksevery once in a while.