Template Attribute Inheritance#
Template attribute inheritance is a runtime resolution mechanism: when any code reads a note's attributes, Trilium dynamically walks the note's ~template and ~inherit relations and merges the template's attributes into the effective attribute set. Nothing is copied to the note at creation time — the inheritance is recomputed (and then cached) on every fresh read.
This is distinct from how templates are applied at note creation (see note_create.ts); that service sets up the initial ~template relation but does not copy any attributes.
How Resolution Works#
The core logic lives in two parallel implementations — one on the server, one on the client — but they follow the same algorithm.
Server side — BNote.__getAttributes() (bnote.ts)#
__getAttributes(path) is the authoritative implementation. It runs in three phases:
- Collect owned + inheritable-parent attributes — starts with the note's own attributes, then walks every non-search parent and pulls in their inheritable attributes (skipping
rootand_hiddento avoid polluting the hidden subtree) . - Follow template/inherit relations — scans the accumulated attribute list for any
relationattributes namedtemplateorinherit, then recursively calls__getAttributeson those target notes to pull in their full attribute set . - Deduplicate — builds the final cache using an
addedAttributeIdsSet so that the same attribute is included only once even if it is reachable via multiple inheritance paths .
Two labels are explicitly filtered out during step 2 to prevent the template marker from propagating to instances: #template and #workspacetemplate .
Cycle protection is handled by the path parameter — if a note ID already appears in the current traversal path, recursion returns [] immediately .
Client side — FNote.__getCachedAttributes() (fnote.ts)#
The frontend mirrors the same algorithm in __getCachedAttributes(path). The structure is identical: collect owned attributes, pull inheritable parent attributes, then recurse into template/inherit targets . The same exclusions (#template, #workspacetemplate) and cycle detection apply .
The helper getNotesToInheritAttributesFrom() surfaces the full list of template/inherit relation targets for a given note.
Caching and Invalidation#
Server (BNote)#
The server maintains two per-note caches populated in the same pass: __attributeCache (all resolved attributes) and __inheritableAttributeCache (only the inheritable subset), both stored directly on the BNote instance .
Invalidation is surgical:
invalidateThisCache()— clears caches only for the current note; used when a change does not affect descendants.invalidateSubTree()— recursively clears caches down the entire child subtree and alongtemplate/inheritrelations, because a template change propagates to all instances.
These are triggered by becca_loader.ts whenever branches or attributes are created, updated, or removed — choosing between the two strategies based on whether the attribute is marked isAffectingSubtree or the note is used as an inherited source.
Client (noteAttributeCache)#
The client uses a single global cache (note_attribute_cache.ts) keyed by note ID. Its invalidate() method wipes the entire cache — a coarser strategy that trades precision for simplicity. Any write event (attribute, branch, or note change) triggers a full invalidation.
Key Behaviors#
| Scenario | Result |
|---|---|
Note has ~template → T | All of T's attributes (minus #template/#workspacetemplate) are visible on the note |
Note has ~inherit → N | Same as ~template — treated identically during traversal |
Template T also has ~template → T2 | T2's attributes are transitively inherited (recursive traversal) |
| Diamond inheritance (two paths to same attribute) | Attribute appears exactly once (deduplication via addedAttributeIds) |
Cycle (A ~template B ~template A) | Cycle is detected via path tracking; returns [] and logs a warning |
root or _hidden notes | Inheritable parent attributes are skipped for these notes |
Key Files#
| File | Role |
|---|---|
packages/trilium-core/src/becca/entities/bnote.ts | Server-side resolution (__getAttributes, invalidateSubTree) |
apps/client/src/entities/fnote.ts | Client-side resolution (__getCachedAttributes, getNotesToInheritAttributesFrom) |
apps/client/src/services/note_attribute_cache.ts | Client-side attribute cache and global invalidation |
packages/trilium-core/src/becca/becca_loader.ts | Triggers server-side cache invalidation on data changes |
apps/client/src/entities/fnote.spec.ts | Unit tests covering inheritance, cycles, deduplication, and marker filtering |