emacs · org-roam

Task management with org-roam Vol. 4: Automatic tagging

Sharing how I automated task tagging in org-roam with vulpea. When you mention someone in a task by linking to their note, the task automatically gets tagged with that person's name. I'm using vulpea-insert hooks to handle this cleanly, avoiding the need for advice on org-roam functions. The code includes checks to ensure we only add person tags to actual TODO items.


In the previous article we covered automatic tagging of notes related to a specific person, and today we are going to cover automatic tagging of an org-mode heading upon insertion of link related to a person. To put it simple, when I mention someone in the task, I would love this task to be automatically tagged with that persons name. As they say, it's better to see once, than imagine multiple times, so here is a screencast.

some random image you can find on barberry garden

Change Log:

  • [2021-01-24 Sun]: Since some of the functionality mentioned in the original article was merged to org-roam, all code is updated to reflect the current state of affairs.
  • [2021-03-02 Tue]: Update naming convention to match personal configurations.
  • [2021-05-10 Mon]: Update post to reflect changes in org-roam v2. Previous version of this article is available on GitHub.
  • [2021-11-19 Fri]: Update post to reflect inclusion of vulpea-insert function to vulpea library. You can find previous version of this article in git history.

Related posts

  • Task management with org-roam Vol. 7: Capture – Sharing my org-roam capture workflow, focusing on quick task entry and efficient processing. I've set up a dedicated inbox file per machine to avoid sync issues, and use org-agenda with the REFILE tag to process captured items. For meetings, I've created a smarter capture template that automatically places one-on-one notes in the right person's file - a nice example of using org-roam's query capabilities to streamline capture.
  • Task management with org-roam Vol. 6: Select a person and view related tasks – Sharing a quick utility I wrote to view all tasks related to a specific person in org-roam. By combining vulpea's selection functions with org-agenda's tag matching, we can easily see everything tagged with a person's name (including their aliases). Just a few lines of code, but it makes a big difference in managing person-related tasks!
  • Task management with org-roam Vol. 5: Dynamic and fast agenda – Sharing a significant performance optimization I created for org-roam agenda generation. By dynamically tracking notes containing TODOs with a 'project' tag, I reduced agenda loading from over 50 seconds to under 1 second. The solution automatically updates tags when files are visited or saved, querying only relevant files when building the agenda view. I've included all the code and explained the implementation details, from checking for TODO entries to SQL queries.
  • Task management with org-roam Vol. 3: FILETAGS – Sharing my approach to managing person-related tasks in org-roam notes. I explain how to use filetags to automatically tag all tasks in a person's note (like @FrodoBaggins), maintaining the tag inheritance functionality from regular org-mode while moving to individual roam notes. I've included code to automate the tagging process, making it easier to maintain consistency when adding new people notes.
  • Task management with org-roam Vol. 2: Categories – Sharing how I improved the agenda view for org-roam notes by fixing category display. Rather than showing file IDs, we can now show meaningful categories based on note titles or explicit CATEGORY properties. I've included a function to automatically extract and format categories, plus options to handle long titles gracefully. The result is a cleaner, more readable agenda that better integrates with org-roam's note-taking approach.
  • Task management with org-roam Vol. 1: Path to Roam – Sharing my approach to task management in org-roam, showing how to organize tasks and projects across notes while maintaining compatibility with org-mode's agenda features. While org-roam favors smaller files over larger ones, we can still implement a familiar structure of tasks, projects, and meta-projects. The article explains the basic setup, though there are some visual quirks in the agenda buffer that we'll address in a follow-up post.

Once could just write an advice for org-roam-node-insert by using a relatively recent change that makes this function to return what was inserted. This also uses name manipulation from the previous article and tags lookup from Org-roam tags article.

(defun org-roam-node-insert-wrapper (fn) "Insert a link to the note using FN. If inserted node has PEOPLE tag on it, tag the current outline accordingly." (interactive) (when-let* ((node (funcall fn)) (title (org-roam-node-title node)) (tags (org-roam-node-tags node))) (when (seq-contains-p tags "people") (save-excursion (ignore-errors (org-back-to-heading) (org-set-tags (seq-uniq (cons (vulpea--title-to-tag title) (org-get-tags nil t))))))))) (advice-add #'org-roam-node-insert :around #'org-roam-node-insert-wrapper)

The implementation is straight-forward. We start with calling fn (e.g. org-roam-node-insert) that asks for the note to insert. Then we parse result and query the roam tags to understand if the inserted note is related to a person. And if the answer is yes, we use org-set-tags to automatically tag the heading.

And while advicing is powerful tool and allows us to solve the problem, there is slightly different, less intrusive and composable solution provided by vulpea library - vulpea-insert function that acts like org-roam-node-insert, but provides ability setup hooks on insertion. First, we define a handler (pretty much the same as org-roam-node-insert-wrapper but without any calls to insertion function).

(defun my-vulpea-insert-handle (note) "Hook to be called on NOTE after `vulpea-insert'." (when-let* ((title (vulpea-note-title note)) (tags (vulpea-note-tags note))) (when (seq-contains-p tags "people") (save-excursion (ignore-errors (org-back-to-heading) (when (eq 'todo (org-element-property :todo-type (org-element-at-point))) (org-set-tags (seq-uniq (cons (vulpea--title-to-tag title) (org-get-tags nil t))))))))))

And then you just need to add it as a hook:

(add-hook 'vulpea-insert-handle-functions #'my-vulpea-insert-handle)

With this approach you can add as many handlers as you wish without the need to grow your advice/wrapper too much.

Complete solution

(defun my-vulpea-insert-handle (note) "Hook to be called on NOTE after `vulpea-insert'." (when-let* ((title (vulpea-note-title note)) (tags (vulpea-note-tags note))) (when (seq-contains-p tags "people") (save-excursion (ignore-errors (org-back-to-heading) (when (eq 'todo (org-element-property :todo-type (org-element-at-point))) (org-set-tags (seq-uniq (cons (vulpea--title-to-tag title) (org-get-tags nil t)))))))))) (defun vulpea--title-to-tag (title) "Convert TITLE to tag." (concat "@" (s-replace " " "" title))) (add-hook 'vulpea-insert-handle-functions #'my-vulpea-insert-handle)

References