Targeting Model
Bowerbird uses a unified targeting model across all commands that operate on sets of notes. Once you understand the four selectors, you can use any command.
Core principle: All targeting selectors compose via AND (intersection). Each selector narrows the set of matched files.
The Four Selectors
Section titled “The Four Selectors”Type (-t, --type <type>)
Section titled “Type (-t, --type <type>)”Filter by schema type.
bwrb list --type taskbwrb bulk --type reflection --set reviewed=truebwrb audit --type objectiveBehavior:
- Accepts short names (
task) or full paths (objective/task) - Short names are unambiguous since types cannot share names
- When specified, enables strict validation of
--wherefields
Positional shortcut: Type can be provided as a positional argument:
bwrb list task # Same as: bwrb list --type taskbwrb bulk task --set x=y # Same as: bwrb bulk --type task --set x=yPath (-p, --path <glob>)
Section titled “Path (-p, --path <glob>)”Filter by file location in the vault.
bwrb list --path "Ideas/"bwrb bulk --path "Reflections/Daily Notes" --set status=reviewedbwrb audit --path "Work/**"Behavior:
- Accepts directory paths or glob patterns
- Paths are relative to vault root
- Supports glob syntax (
*,**,?)
Query (-w, --where <expression>)
Section titled “Query (-w, --where <expression>)”Filter by frontmatter field values.
bwrb list --where "status == 'active'"bwrb bulk --where "priority < 3 && !isEmpty(deadline)" --set urgent=truebwrb audit --where "isEmpty(tags)"Behavior:
- Supports comparison operators:
==,!=,<,>,<=,>= - Supports regex match operator:
=~(e.g.,name =~ '^\\[ERROR\\]') - Supports boolean operators:
&&,||,! - Supports functions:
isEmpty(),contains(),startsWith() - Multiple
--whereflags are ANDed together - Field names may include hyphens and are treated literally in
--where(e.g.,creation-date == '2026-01-28'). - System fields are always available:
name(falls back to filename) andid.
Type-checking behavior:
- With
--type: every field reference is validated against the type’s schema, including function arguments such as the relation field passed tounder(); unknown fields and invalid select values are errors - Without
--type: unknown fields are permissive (no unknown-field validation) - In all modes: invalid expression syntax and runtime expression errors are hard errors
Hierarchy functions (for parent-child note relationships):
isRoot()— note has no parent-like note link of any kind (for exampleparent,owner, or a singular relation likemilestone)isChildOf('[[Note]]')— direct child of the specified note (oneparenthop)isDescendantOf('[[Note]]')— the note is transitively beneath the specified note via itsparentchain (any depth)under(field, '[[Note]]')— the note’sfieldrelation points atNoteor any descendant ofNote
bwrb list --type task --where "isRoot()"bwrb list --type task --where "isChildOf('[[Epic]]')"bwrb list --type task --where "isDescendantOf('[[Q1 Goals]]')" --depth 2bwrb list --type task --where "under(context, '[[career]]')"How the three operators differ
Section titled “How the three operators differ”| Operator | What it reads | Direct vs transitive |
|---|---|---|
isChildOf('[[X]]') | the note’s own structural parent | direct (one hop) |
isDescendantOf('[[X]]') | the note’s own structural parent chain | transitive (any depth) |
under(field, '[[X]]') | a relation field dereferenced to another note, then that target’s parent chain | transitive, inclusive of the direct target |
The key split: isChildOf/isDescendantOf walk the parent field of the
note itself (the same chain --output tree renders), while under follows an
arbitrary relation field to another note and walks its ancestry. under
is therefore the operator for relation fields such as context or milestone —
those are not treated as a structural parent by isChildOf/isDescendantOf
(see the isRoot note below).
isChildOf/isDescendantOf resolve the parent chain over the whole vault,
not just the filtered candidate set. So a chain that climbs through a note of
a different type — e.g. a task whose parent is a milestone that
--type task filters out — is followed all the way to the true ancestor instead
of stopping early at the filtered-out note.
isChildOf and isDescendantOf are alias-aware on both sides, just like
under (see below): a note whose parent is written as an alias of the real
parent (parent: "[[BuilderProject]]" where BuilderProject is an alias of
Builder) still matches isChildOf('[[Builder]]') /
isDescendantOf('[[Ancestor]]'), and an aliased query node
(isChildOf('[[BuilderProject]]')) resolves to the canonical note. Aliases are
canonicalized at every step of the walked parent chain. Ambiguous aliases
(declared by more than one note) and dangling aliases are left literal, so they
simply don’t match rather than guessing; cycles remain safe.
under(field, '[[Node]]') vs isDescendantOf('[[Node]]')
Section titled “under(field, '[[Node]]') vs isDescendantOf('[[Node]]')”Both ask a hierarchy question, but they walk different chains:
isDescendantOf('[[Node]]')walks the filtered note’s own literalparentchain. It answers “is this note structurally beneathNode?” (for example task → milestone → objective, when those links are written asparent).under(field, '[[Node]]')first dereferences a relationfieldon the note, then walks that target’sparentchain. It answers “does this note’sfieldpoint into the subtree rooted atNode?”
Example. Contexts are real notes in a parent hierarchy
(Builder.parent = [[career]], Vercel.parent = [[Builder]]), and a task
records only the leaf:
# a taskcontext: "[[Vercel]]"# Everything in the career domain, at any altitude — Builder, Vercel, deeper.bwrb list --type task --where "under(context, '[[career]]')"
# Exact context only.bwrb list --type task --where "context = [[Vercel]]"Notes:
- Inclusive of the direct target.
under(context, '[[career]]')matches a note whosecontextis[[career]]itself as well as any descendant of career. (under(field, '[[X]]')≡field = [[X]]ORfieldpoints to a descendant ofX.) - Any relation field. The first argument is the field to dereference —
under(milestone, '[[Q2]]'),under(owner, '[[Platform]]'), etc. When--typeis set, the first argument is validated against the schema: an unknown field, or a field that is not a relation, is flagged up front (e.g.under() expects a relation field, but 'status' is a 'select' field) rather than silently matching nothing. - Multi-valued fields. If the relation holds a list, the note matches when any target is under the node.
- Resolution scope.
underresolves relation targets and their ancestors across the whole vault, so the target notes do not need to share the filtered type. When the relation field has asourceconstraint, bare same-name targets are resolved by that source type before walking the target’s ancestor chain; cross-type name collisions are ignored when exactly one target matches the source type. Multiple matching source-type notes remain ambiguous and should be path-qualified. - Alias-aware.
undercanonicalizes aliases (see Schema) on both sides — an aliased relation value (context: "[[BuilderProject]]"whereBuilderProjectis an alias ofBuilder) and an aliased query node (under(context, '[[BuilderProject]]')) both resolve to the canonical note, using the same resolution asbwrb list --name <alias>. An ambiguous alias (declared on more than one note) is not auto-resolved — it matches nothing rather than silently picking a winner. - Robustness. A dangling (unresolvable) relation target — including a
dangling alias — simply doesn’t match; a malformed
parentcycle is walked safely and never loops forever.
For the full pattern — modelling life domains and projects as a parent tree of
context notes and collapsing a redundant scope field into it — see
Hierarchical Scope (Contexts as Notes).
Body (-b, --body <query>)
Section titled “Body (-b, --body <query>)”Filter by literal Markdown body content (full-text search via ripgrep). YAML frontmatter is excluded.
bwrb list --body "TODO"bwrb bulk --body "DEPRECATED" --delete deprecated_fieldbwrb list --body "meeting notes" --matches --type taskBehavior:
- Searches Markdown body content only; use
--wherefor frontmatter fields - Detailed matches keep original file line numbers and body-only context
- Uses ripgrep under the hood for performance
- Case-insensitive by default
Selector Composition
Section titled “Selector Composition”All selectors compose via AND (intersection). Each additional selector narrows the result set.
# Find tasks in Work/ folder with status=active containing "deadline"bwrb list --type task --path "Work/" --where "status == 'active'" --body "deadline"Union (OR) is not implicit. To express OR logic, use boolean operators within --where:
bwrb list --where "status == 'draft' || status == 'review'"Smart Positional Detection
Section titled “Smart Positional Detection”For ergonomics, the first positional argument is auto-detected:
| Input | Detection | Equivalent |
|---|---|---|
bwrb list task | Matches known type | --type task |
bwrb list "Ideas/" | Contains /, matches path | --path "Ideas/" |
bwrb list "status == 'x'" | Contains operators | --where "status == 'x'" |
Ambiguity handling: Read-only list modes may return multiple rows. A mutation never chooses one silently: ambiguity errors list candidate paths and tell you to retry with one exact vault-relative path (or narrow with explicit selectors).
Command Support Matrix
Section titled “Command Support Matrix”| Command | --type | --path | --where | --body | Picker |
|---|---|---|---|---|---|
| edit | Y | Y | Y | Y | Y |
| delete | Y | Y | Y | Y | - |
| list | Y | Y | Y | Y | Y |
| audit | Y | Y | Y | Y | - |
| bulk | Y | Y | Y | Y | - |
Notes:
listis the canonical query, search, picker, and open surface.
Default Behavior
Section titled “Default Behavior”Default behavior depends on command destructiveness:
Read-only commands (list, audit without --fix)
Section titled “Read-only commands (list, audit without --fix)”No selectors = implicit --all (operate on entire vault).
bwrb list # Lists all notesbwrb audit # Audits all notesInteractive commands (open, edit)
Section titled “Interactive commands (open, edit)”No selectors = prompt with picker.
Destructive commands (bulk, delete)
Section titled “Destructive commands (bulk, delete)”Two safety gates:
- Targeting required: No selectors = error. Must specify at least one selector OR explicit
--all. - Execution required: Dry-run by default. Must use
--executeto apply changes.
bwrb bulk --set status=settled# Error: No files selected. Use --type, --path, --where, --body, or --all.
bwrb bulk --type task --set status=settled# Dry-run: shows what would change, but doesn't apply
bwrb bulk --type task --set status=settled --execute# Actually applies the changes
bwrb bulk --all --set status=settled --execute# Works (explicit targeting + explicit execution)This two-gate model prevents accidental vault-wide mutations.
Exception: audit --fix
Section titled “Exception: audit --fix”bwrb audit --fix is a remediation workflow. It still requires explicit targeting (at least one selector or --all). Interactive fixes write by default; auto-fixes require --execute to apply.
- Targeting required: No selectors = error. Must specify at least one selector OR explicit
--all. - Interactive preview: Use
--dry-runto preview guided fixes without writing. - Non-interactive: Use
--fix --auto --executefor safe auto-fixes when stdin is not a TTY (omit--executeto preview).
See also: CLI Safety and Flags
Output Formats
Section titled “Output Formats”Use the long --output <format> option when you want portable command syntax.
On note workflows such as new, edit, list, and recent, -o means
--open. A few management subcommands, including schema migrate and
dashboard run, use -o for --output; check the command’s help before using
the short form.
| Format | Description |
|---|---|
text | Default human-readable |
json | Machine-readable JSON |
paths | File paths only |
link | Wikilinks ([[Note Name]]) |
tree | Hierarchical tree view (list only) |
content | Full file contents (list) |
bwrb list --type task --output jsonbwrb list --type task --output pathsbwrb list --type task --output link # [[Task 1]], [[Task 2]], ...bwrb list --type task --output tree # Hierarchical displaybwrb list --name "TODO" --output content # Full resolved filePagination (interactive picker)
Section titled “Pagination (interactive picker)”When a command needs you to pick from multiple results, Bowerbird uses an interactive picker. The pagination keys below apply to the numbered picker (and auto when it falls back to numbered). If you use --picker fzf, fzf provides its own navigation.
- Page size: 10 items (pagination appears when there are more than 10 options)
- Keys:
-previous page,+/=next page 1-9selects items 1-9 on the current page0selects item 10 on the current pageUp/Down(orj/k) moves the highlightEnterconfirms the highlighted itemCtrl+C/Escapecancels the picker
--output json is never interactive and never paginates.
See Also
Section titled “See Also”- CLI Safety and Flags —
--executevs--forcesemantics - Schema — Query expression details
- bwrb list — List and filter notes
- bwrb bulk — Batch operations