Basic Memory
Concepts

Metadata Search

Filter and find notes by their YAML frontmatter — status, type, tags, priority, confidence, custom fields, and more.

Every note in Basic Memory has YAML frontmatter at the top. Metadata search lets you find notes by those fields — status, type, tags, custom fields, anything you put there. It's how you ask things like "show me all in-progress specs tagged security" or "find decisions made in the last quarter with priority high or critical" without scanning text.

Metadata search is a parameter on the same search_notes tool you use for text and semantic search. You can use it on its own, or combine it with a text query and tag filters.

Frontmatter is structured data. The richer your frontmatter, the sharper your search. A note with type: meeting, status: open, priority: high, and attendees: [...] is reachable from many more angles than one with just a title.

What's in your frontmatter

Anything you put in the YAML block at the top of a note becomes searchable:

---
title: Auth Design
type: spec
tags: [security, oauth]
status: in-progress
priority: high
confidence: 0.85
---

A few fields have shortcuts in search_notes (tags, status, type), but all custom fields work the same way through metadata_filters. You can also reach into nested objects with dot notationschema.confidence targets the confidence key inside a schema object.

Field names must match the chars A-Z a-z 0-9 _ - (with . for nesting). Field values can be strings, numbers, booleans, dates, or lists.

Filter syntax

metadata_filters is a JSON object. Each key is a field name; each value is either a literal (for equality) or an operator object.

Multiple top-level keys combine with AND — all conditions must match.

Equality

{"status": "in-progress"}

Array contains (all values present)

For list-valued fields like tags, an array means "all of these":

{"tags": ["security", "oauth"]}

$in — any of

Match if the field equals any value in the list. This is how you express OR within a single field:

{"priority": {"$in": ["high", "critical"]}}

Comparison — $gt, $gte, $lt, $lte

Numeric or lexicographic comparison; boundaries on $gte/$lte are inclusive.

{"confidence": {"$gt": 0.7}}
{"score": {"$lte": 100}}

$between — inclusive range

Both ends included; the value must be exactly two items:

{"confidence": {"$between": [0.5, 0.9]}}

Nested fields (dot notation)

{"schema.confidence": {"$gt": 0.7}}

Operator reference

OperatorValueBehavior
(scalar)string / number / bool / dateExact match
(array)list of scalarsAll values must be present (for list fields)
$innon-empty listAny of
$gt, $gte, $lt, $ltenumberComparison (gte/lte inclusive)
$between[min, max]Inclusive range, exactly 2 values
Each operator object holds one operator. {"$gt": 0.5, "$lt": 1.0} is invalid — use {"$between": [0.5, 1.0]} instead. There's no $ne, $nin, $regex, or $exists; combine multiple top-level keys with AND, and use $in for OR within a field.

Convenience shortcuts

search_notes exposes a few common metadata filters as top-level parameters:

ShortcutEquivalent filter
tags=["security"]{"tags": ["security"]}
status="draft"{"status": "draft"}
note_types=["spec", "decision"]type filter (any of)

You can also write a tag filter directly in the query string:

# All equivalent
search_notes("tag:security")
search_notes(tags=["security"])
search_notes(metadata_filters={"tags": ["security"]})

Multiple tags in the shorthand mean all of them:

search_notes("tag:tier1,alpha")   # comma-separated
search_notes("tag:tier1 alpha")   # space-separated
# Both require BOTH tags to be present.

If a key appears in both a shortcut and metadata_filters, the explicit filter wins.


Metadata filters compose with everything else search_notes does. The text query and the filters apply together (AND):

# OAuth-related notes that are still in progress
search_notes("OAuth", metadata_filters={"status": "in-progress"})

# Pure metadata — no text query
search_notes(metadata_filters={"type": "spec", "status": "active"})

# Text + tag shortcut + metadata
search_notes("auth", tags=["security"], metadata_filters={"priority": {"$in": ["high", "critical"]}})

See Semantic Search for how text and vector modes interact.


From the CLI

bm tool search-notes mirrors the same surface:

# Simple equality
bm tool search-notes "" --meta status=draft

# Multiple --meta flags AND together
bm tool search-notes "" --meta status=active --meta priority=high

# Full JSON filter
bm tool search-notes "" --filter '{"confidence": {"$between": [0.3, 0.8]}}'

# Convenience shortcuts
bm tool search-notes "auth" --tag security --tag oauth --status draft --type spec

See CLI Reference for every flag.


Worked examples

Given a few notes like this:

---
title: Auth Design
type: spec
status: in-progress
priority: high
tags: [security, oauth]
confidence: 0.85
---
---
title: Search Redesign
type: spec
status: planning
priority: medium
tags: [search, performance]
confidence: 0.6
---
You want…metadata_filters
All in-progress specs{"status": "in-progress", "type": "spec"}
Specs with confidence above 0.7{"type": "spec", "confidence": {"$gt": 0.7}}
Anything high or critical priority{"priority": {"$in": ["high", "critical"]}}
Specs in confidence range 0.5, 0.9{"type": "spec", "confidence": {"$between": [0.5, 0.9]}}
Notes tagged both security AND oauth{"tags": ["security", "oauth"]}
"OAuth" matches in still-open workuse search_notes("OAuth", metadata_filters={"status": "in-progress"})
Anything with nested schema.confidence ≥ 0.7{"schema.confidence": {"$gte": 0.7}}

Type handling

  • Strings match exactly (case-sensitive).
  • Numbers — comparison operators detect numeric values automatically, so "0.5" and 0.5 compare the same way.
  • Booleans are compared as the strings "True" and "False".
  • Dates and datetimes are normalized to ISO strings.
  • Missing fields never match — filtering on a key the note doesn't have returns no result for that note (not a null match).

Limits and caveats

  • AND only at the top level. Combine multiple keys to AND; use $in for OR within a single field. There's no top-level $or.
  • No full-text matching inside metadata. Use exact, range, or $in matching — for free-text search across note bodies, use text or semantic search.
  • Lists in filters must be non-empty. {"tags": []} is rejected.
  • $between requires exactly two values.
  • No $exists, $ne, $nin, $regex. They're not in the operator set.