Bases: support recursive/transitive filtering on frontmatter properties (e.g. parent-child hierarchies)

Use case

Many vaults use a parents multitext property to build hierarchies — projects with sub-projects, places with sub-places, org charts, etc:

# Project A (root)
parents: []

# Project B (child of A)
parents: "[[Project A]]"

# Project C (grandchild — child of B)  
parents: "[[Project B]]"

In a Bases view embedded in Project A, I want to see all descendants — not just direct children, but grandchildren, great-grandchildren, etc.

What works today

Direct children (one level deep):

filters:
  and:
    - parents.contains(this.file.name)

This works perfectly. It returns all notes where parents includes the current note’s name.

What doesn’t work

Any attempt at recursive filtering fails because you can’t access frontmatter properties from within a filter() chain.

Attempt 1 — traverse via file.links

# Degree 2: grandchildren
file.links.filter(value.asFile().parents.contains(this.file.name)).length > 0

Error: Cannot find parents on type FileasFile() returns a File object which doesn’t expose frontmatter properties like parents.

Attempt 2 — traverse via parents directly

parents.filter(value.asFile().parents.contains(this.file.name)).length > 0

Fails silently — multitext property values don’t support asFile() resolution.

Attempt 3 — use the result of one filter as input to another

parents.containsAny(parents.contains(this.file.name))

Doesn’t workparents.contains() returns a boolean, not a list of matching notes. There’s no way to capture “the set of notes matching filter X” and feed it into filter Y.

The gap

The filter engine already evaluates parents.contains(this.file.name) across every note in the base — it knows which notes are direct children at evaluation time. But it discards that intermediate result. If it could expose that result set as a reusable list, recursive hierarchies would work:

# Pseudocode for what would solve this
filters:
  or:
    - parents.contains(this.file.name)                    # direct children
    - parents.containsAny(matchingNames(parents.contains(this.file.name)))  # grandchildren

Or equivalently, if asFile() could access frontmatter:

# If this worked, recursion would be straightforward
file.links.filter(value.asFile().note.parents.contains(this.file.name)).length > 0

Proposed solution

  1. containsTransitive(property, value) — a built-in that walks a named property chain recursively. E.g. parents.containsTransitive(this.file.name) returns true if this appears anywhere in the ancestor chain via parents

  2. Expose frontmatter from asFile() — let value.asFile().propertyName or value.asFile().frontmatter.propertyName access note properties. This would make the degree-2+ filters work with the existing filter() syntax

  3. Result-set reuse — let a filter expression return the set of matching file names (not just a boolean), so it can be used as input to containsAny() or similar

Option 1 is the most user-friendly. Option 2 is the most general (enables many other use cases). Option 3 is the most flexible but potentially complex to implement.

Why this matters

Parent-child hierarchies are one of the most common structures in personal knowledge management:

  • Project management — projects → sub-projects → tasks (my use case — ~70 project notes, 3 levels deep)

  • Places — country → city → building → room

  • Organisations — company → department → team

  • Academic notes — topic → subtopic → concept

Currently, Bases can only show one level of these hierarchies. Showing the full tree requires Dataview — which undermines the value of Bases as a native, no-plugin solution.

Environment

  • Obsidian 1.8.x

  • Bases (core plugin, latest)

  • macOS / iOS

You can … by accessing the properties object available on file :blush:… as stated somewhere here: Bases syntax > File properties

Property Type Description
file.properties Object All properties on the file. Note: Does not automatically refresh results when the vault is changed.

When coercing a link into a file within filter() or map(), for example, only the implicit keys ( file.name, file.size, etc…) are accessible directly…
So if you need to access keys stored in YAML/Properties, you can dereference them from the file.properties object.

So, as you’ve seen, this doesn’t work…

While this should…

file.links.filter(value.asFile().properties.parents.contains(this.file.name)).length > 0

Now, I’m not invalidating your request :smile:, as it is indeed not necessarily easy to loop through some levels of parent/child structure (example 1 and in the same topic: example 2… Although the use case is different from the one here, I think the principles would still apply :blush: ) but I feel like the premise isn’t the right one :innocent:

A big side note regarding this:

file.properties has only been implemented in Obsidian 1.9.7 (Obsidian 1.12.7 is the latest public release)

So if you can, you might want to update Obsidian (and/or its installer (on desktop)) :blush:

Thanks @Pch !

First, my bad on the version - I’m already on 1.12.7, don’t know why I put 1.8.

Thanks for the .properties. tip — that does resolve accessing frontmatter from within file.links.filter(). However the false-positive problem remains.

file.links traverses all outgoing links from a note, not just the parents property. So degree 2+ matches notes that mention a child of this anywhere in their body text, not just notes whose parents chain leads back to this.

For example, in my vault a project note for “Project A” shows unrelated “Project B” research notes and unrelated “Project C” development notes as “sub-projects” — because those notes happen to mention a child of “Project A” somewhere in their body, even though their parents property has nothing to do with “Project A”.

What’s needed is either:

  1. parents.filter(value.asFile()...) working on multitext property values — currently multitext values don’t support .asFile(), so you can’t traverse specifically through the parents chain. This would let us write:

    parents.filter(value.asFile().properties.parents.contains(this.file.name)).length > 0
    

    which scopes the traversal to the parents hierarchy only.

  2. Or a built-in transitive function like containsTransitive() that walks a named property chain recursively.

The feature request still stands — .properties. is a piece of the puzzle (thanks for pointing it out!) but doesn’t solve the core problem of scoping the traversal to a specific property rather than all links.

1 Like