Group links/tags in org-roam

Total newbie to org-roam here. Is there a way to group nodes in org-roam in the style of group tags in org-mode but across the whole database. I want to incorporate polyhierarchy in my Zettelkasten in order to summon information on the spot by filtering my database to a handful of nodes (top-down approach as opposed only having the option to navigate laterally from link to link to get to the information I want). Example: #animal (group tag for #cat #dog, etc.) AND #color (group tag for #red #black, etc.) would return nodes about colored animals like “Black cat” or “Purple dogs” without ever explicitly using the word “animal” or “color”, hence the taxonomies in the background.

P.S. I’ve used tags to illustrate my use case but I would want the taxonomies to apply to links as wells, whenever I’m refering to a concept really. I’m assuming tags and links are interchangeable.

So, I understand your use case here but the way you are thinking about it doesn’t really follow the zettelkasten philosophy on which org roam is based. The idea is you don’t need hierarchy because everything is so well linked with one another.

However, the use case you are describing is something that needs a solution and I have an idea for you. To my understanding, you essentially want to group nodes with these tags which are much wider in scope than the tags you already use.

The zettelkasten-y way of doing this is instead of using tags or something like that, create a node in your system with the name “Animals” and one with the name “Color” and then link every node related to those to these. These will be your “index” nodes, also known as entry points or Maps of Contents (MOCs) by some people. Then, you can look at their back links with the org-roam buffer, or use a backlinks searcher sort of thing.

Due to the flexibility of org roam filter functions, you can then create custom query functions with these. I have on my config for example a small section about a recursive backlinks searcher where you get prompted for a node and see its backlinks recursively. Building on that, you can even make a function that accepts n nodes and then runs org-roam-find on all nodes that are backlinked to every selected node. Possibilities there are basically endless. I can even help you do something like that if you want.

1 Like

Why Org vs Org-roam? If you have confirmed that Org-mode does what you like, I would think you could use it for your Org-roam notes so you keep both.

You are right actually. I can still do org-mode tagging in org-roam! I’m just dissapointed I can’t do the same nesting/grouping with links or can I?

Is this below what you’re going for? I believe this gives you “taxonomies in the background”.

I don’t know if this data structure “does” anything special for you in the current Org-roam implementation. If I remember correctly, Org-roam-ui understands the hierarchical structure within one file like you see in Animal and Color org files and render them accordingly. Something you might like to see for yourself.

Index file for Animal
:ID: A
#+title: Animal
#+filetags: :index:

* Cat
:ID: A.1

* Dog
:ID: A.2
Index file for Color
:ID: C
#+title: Color
#+filetags: :index:

* Red
:ID: C.1

* Black
:ID: C.2
Note File
:ID: N
#+title: Note on a black dog

Today I saw a [[id:A.2][dog]]. It was [[id:C.2][black]]. 

Thank you for that. I perfectly understand the philosophy behind Zettelkasten but I find it to be limiting. I see tags/links as a level of abstracted concept (not as a mere reference to something). I need to be able to use broader tags/links which encompass narrower ones as another layer of abstraction and keep moving up the tree of knowledge.

The solution you proposed might just work using custom query functions. Excuse my using layman terms since I’m not a programmer: is there a way, using #animal, to return all n nodes linked to it or any subnode I’ve specified, not knowing in advanced how many levels deep I’ve gone? e.g. animal/mammal/cow It would have to go down the tree until it touches all terminal nodes.

The next step is for me to look for the intersection between taxonomies: What nodes do I have that relate to #mammal AND #philosophy (or a specific subset of philosophy). I want to be in complete control of the level of abstraction for each component. If my main brain can do it, I want my second brain to be able to.

Interesting! I like this idea of giving each subset an ID. Would I then be able to query Animal AND Color then return “Today I saw a dog. It was black”? but not, say, “Today I saw a dog.” (no reference to color)?

Yes, you should be able to, but you’d need write your own query. There should be many different approaches. One way I could think of is to use org-roam-db-query and write your own SQL query for the database. You can inspect the DB schemata by looking at variable org-roam-db--table-schemata. I think you would only need to use tables nodes and links.

If you always query from the top-level category (“Animal” and “Color” are level 0 = top-level categories), then the logic would get easier (starting from a sub-level would need to only look at that particular subtree – the database does not have this tree information, only the file does). The logic should be something like this below. I am not proficient in SQL and I do not have good sample data, so I won’t be able to spend time to have a prototype.

  1. For given two (or n) top-categories file-node IDs, find their respective file from nodes table

  2. For all the files found in step 1, find all the IDs contained in them from nodes table

  3. For all the IDs found in step 2, find from links table all sources that have at least one ID from each and every category as dest
    (A or A.1 or A.2) and (C or C.1 or C.2) and (N.n) and ...

This should give you all the IDs of the nodes (sources are IDs) that have reference to both an Animal (cat or dog, or animal) and a Color (black or red, or color).

If you’re still up for it, I would love your help on this. Pretty sure it’s exactly what I’m looking to do! Thank you for offering.

Ok, so I played around a bit with this concept and I hacked this little thing. Took me like 3 hours so its definitely not polished or tested amply, but it appears to work fine. The first section is more or less the backbone of the process, which I recommend you take a look at so you get more acquainted with hacking on org-roam (if you know elisp that is, which you definitely should if you want to play around with your config), while the third section are the end-user functions which you will need to run for what you asked.

Note that there are no dependencies besides org-roam for all but the last function. The last function, as mentioned in its docstring is an extension to my aforementioned recursive backlink searcher (org-roam-backlinks-search), which you can also try. The code for it is just above this part.

If you have any issues, feel free to ask, happy to help!


I think I have achieved exactly what you wanted. To do this, you will need to :

  (defun cp/org-get-tags-with-children(tags)
    "Take a list of tag, and return this list of tag WITH the sub-tags (define in org-tag-alist) of each tag in entry"
    (let (tags-result)
      (dolist (tag tags)
        (dolist (tag-to-add (org-tags-expand tag t))
          (push tag-to-add tags-result)))
      (delete-dups tags-result)

  (defun cp/vulpea-select-from-tags-with-children (tags)
    "Takes a list of tags, and allows the user to choose a note that has one of these tags OR has a child tag from the list given in parameter"
    (let ((links (vulpea-db-query-by-tags-some (cp/org-get-tags-with-children tags))))
      (unless links
        (user-error "There are note with the current tag (or children)"))
       :candidates-fn (lambda (_) links)
       :require-match t))

Then, to give you an example of how to use it, you can do this:
(cp/vulpea-select-from-tags-with-children '("Persp"))

Finally, to make it more user-friendly, you should create a function that allows you to select several tags from the list of all your tags, and pass it as a parameter to the “cp/vulpea-select-from-tags-with-children” function.

PS : There is also the “vulpea-db-query-by-tags-every” function if you ever need it.

A universal solution to problems like these would only need to express six primitives:

  • query for files based on titles and/or tags. (I find TITLE and ROAM_ALIASES sufficient; I never use org-style tags.)
  • query for a node’s children
  • query for a node’s parents
  • take the disjunction (OR) of queries
  • take the conjunction (AND) of queries
  • permit parenthetical grouping of queries

The OP’s question could be stated using these primitives as child of (child of "animals") AND child of (child of "colored things") (assuming animals and colored things are two node titles).

Am I correct that no such thing yet exists?

For what it’s worth, I implemented a query library (inspired by org-ql) for this: org-roam-ql

1 Like

@ahmed-shariff This looks cool as hell! I can’t wait to try it. Thanks!

1 Like