This adds structured filtering to Basic Memory's search. Instead of searching note content, you query custom frontmatter fields like status, priority, or confidence scores using equality, range, and array operators. Any YAML key you add to frontmatter becomes queryable automatically without configuration. The syntax is straightforward JSON filters passed to search_notes: {"confidence": {"$gt": 0.7}} or {"priority": {"$in": ["high", "critical"]}}. You can combine metadata filters with text search or use them alone. Most useful when you've been disciplined about adding custom fields to your notes and need to find things by their properties rather than their content. Works with dot notation for nested fields and has tag shortcuts for common cases.
npx -y skills add basicmachines-co/basic-memory-skills --skill memory-metadata-search --agent claude-codeInstalls into .claude/skills of the current project.
Find notes by their structured frontmatter fields instead of (or in addition to) free-text content. Any custom YAML key in a note's frontmatter beyond the standard set (title, type, tags, permalink, schema) is automatically indexed as entity_metadata and becomes queryable.
status: draft or priority: highconfidence > 0.7 or score between 0.3 and 0.8All metadata searching uses search_notes. Pass filters via metadata_filters, or use the tags and status convenience shortcuts. Omit query (or pass None) for filter-only searches.
Filters are a JSON dictionary. Each key targets a frontmatter field; the value specifies the match condition. Multiple keys combine with AND logic.
{"status": "active"}
{"tags": ["security", "oauth"]}
$in (match any value in list){"priority": {"$in": ["high", "critical"]}}
$gt, $gte, $lt, $lte){"confidence": {"$gt": 0.7}}
Numeric values use numeric comparison; strings use lexicographic comparison.
$between (inclusive range){"score": {"$between": [0.3, 0.8]}}
{"schema.version": "2"}
| Operator | Syntax | Example |
|---|---|---|
| Equality | {"field": "value"} | {"status": "active"} |
| Array contains | {"field": ["a", "b"]} | {"tags": ["security", "oauth"]} |
$in | {"field": {"$in": [...]}} | {"priority": {"$in": ["high", "critical"]}} |
$gt / $gte | {"field": {"$gt": N}} | {"confidence": {"$gt": 0.7}} |
$lt / $lte | {"field": {"$lt": N}} | {"score": {"$lt": 0.5}} |
$between | {"field": {"$between": [lo, hi]}} | {"score": {"$between": [0.3, 0.8]}} |
| Nested | {"a.b": "value"} | {"schema.version": "2"} |
Rules:
[A-Za-z0-9_-]+ (dots separate nesting levels)$in and array-contains require non-empty lists$between requires exactly [min, max]Warning: Operators MUST include the
$prefix — write$gte, notgte. Without the prefix the filter is treated as an exact-match key and will silently return no results. Correct:{"confidence": {"$gte": 0.7}}. Wrong:{"confidence": {"gte": 0.7}}.
search_notes with MetadataPass metadata_filters, tags, or status to search_notes. Omit query for filter-only searches, or combine text and filters together.
# Filter-only — find all notes with a given status
search_notes(metadata_filters={"status": "in-progress"})
# Filter-only — high-priority specs in a specific project
search_notes(
metadata_filters={"type": "spec", "priority": {"$in": ["high", "critical"]}},
project="research",
page_size=10,
)
# Filter-only — notes with confidence above a threshold
search_notes(metadata_filters={"confidence": {"$gt": 0.7}})
# Convenience shortcuts for tags and status
search_notes(status="active")
search_notes(tags=["security", "oauth"])
# Text search narrowed by metadata
search_notes("authentication", metadata_filters={"status": "draft"})
# Mix text, tag shortcut, and advanced filter
search_notes(
"oauth flow",
tags=["security"],
metadata_filters={"confidence": {"$gt": 0.7}},
)
Merging rules: tags and status are convenience shortcuts merged into metadata_filters via setdefault. If the same key exists in metadata_filters, the explicit filter wins.
The tag: prefix in a query converts to a tag filter automatically:
# These are equivalent:
search_notes("tag:tier1")
search_notes("", tags=["tier1"])
# Multiple tags (comma or space separated) — all must match:
search_notes("tag:tier1,alpha")
A note with custom fields:
---
title: Auth Design
type: spec
tags: [security, oauth]
status: in-progress
priority: high
confidence: 0.85
---
# Auth Design
## Observations
- [decision] Use OAuth 2.1 with PKCE for all client types #security
- [requirement] Token refresh must be transparent to the user
## Relations
- implements [[Security Requirements]]
Queries that find it:
# By status and type
search_notes(metadata_filters={"status": "in-progress", "type": "spec"})
# By numeric threshold
search_notes(metadata_filters={"confidence": {"$gt": 0.7}})
# By priority set
search_notes(metadata_filters={"priority": {"$in": ["high", "critical"]}})
# By tag shorthand
search_notes("tag:security")
# Combined text + metadata
search_notes("OAuth", metadata_filters={"status": "in-progress"})
{"status": "active", "priority": "high"} requires both conditions.query for filter-only searches. search_notes(metadata_filters={"status": "active"}) works without a text query.{"schema.version": "2"} queries the version key inside a schema object.tags and status are sugar for common fields. For anything else, use metadata_filters directly.juliusbrussee/caveman
mattpocock/skills
shadcn/improve
obra/superpowers
forrestchang/andrej-karpathy-skills
vercel-labs/skills