Reference Markdown

Filters

The filter syntax for search, listing, counts and vector queries: equality, one of, not equal, numeric ranges, and how values compare.

A filter keeps only the documents whose meta passes every condition. It is a JSON object: each key is a top-level meta key, and each value is the condition on it. Filters work the same in text search, listing (an empty q), counts, vector search and hybrid queries, over REST and MCP. They're applied before any document is read, so a selective filter makes a query over a big namespace fast and cheap.

curl -s $B/namespaces/support/search -H "Authorization: Bearer $KEY" \
  -d '{"q": "refund", "filter": {"status": ["open", "pending"], "priority": {"gte": 2, "lt": 5}, "team": {"ne": "api"}}}'
hits = ns.search("refund", filter={"status": ["open", "pending"], "priority": {"gte": 2, "lt": 5}, "team": {"ne": "api"}}).hits
const { hits } = await ns.search("refund", { filter: { status: ["open", "pending"], priority: { gte: 2, lt: 5 }, team: { ne: "api" } } });

Conditions

FilterKeeps documents where
{"status": "open"}status equals "open"
{"status": {"eq": "open"}}The same, spelled out
{"status": ["open", "pending"]}status is one of the values
{"status": {"in": ["open", "pending"]}}The same, spelled out
{"team": {"ne": "api"}}team isn't "api": a document without team passes
{"priority": {"gt": 2}}priority is a number greater than 2
{"priority": {"gte": 2, "lt": 5}}A number from 2 up to, not including, 5 (gt, gte, lt, lte combine)
{"ok": true}ok is the boolean true

Every key must hold (AND). For an OR across different keys, run two queries or use SQL.

How values compare

  • Exactly. Strings compare as exact strings: "Open" isn't "open". Normalise the values you filter on when you write them.
  • Numbers as numbers. 3 and 3.0 are equal; the string "3" isn't the number 3.
  • Ranges need numbers, on both sides. gt, gte, lt and lte take a number, and a document whose value isn't a number (or that lacks the key) doesn't pass. Store times as integer seconds since 1970 to filter them by range: {"ts": {"gte": 1760572800}}. An ISO date string can't be range-filtered.
  • Booleans match true and false.
  • Missing keys. A document without the key fails every condition except ne. To find documents that lack a key, use SQL's IS NULL.

Top-level keys only

A filter reads top-level meta keys with scalar values.

  • Nested objects can't be filtered by their fields: {"user": {"plan": "pro"}} is read as an operator named plan and refused. Flatten what you filter on: {"user_plan": "pro"}.
  • Arrays don't match by element. A document with "tags": ["billing", "urgent"] doesn't pass {"tags": "urgent"} (an array equals only the identical array). For tags you filter on, write a boolean each ("tag_urgent": true) and filter {"tag_urgent": true}.

See Data model for shaping metadata.

Where filters go

CallField
POST .../search, POST .../count"filter": {...} in the body
GET .../search, GET .../count?filter= the JSON, URL-encoded
Vector and hybrid search (POST .../search with vector)"filter": {...}: the nearest among the documents that pass
MCP search and countThe filter argument
curl
curl -s -G $B/namespaces/support/search -H "Authorization: Bearer $KEY" \
  --data-urlencode 'q=refund' --data-urlencode 'filter={"status": "open"}'

Count estimates ("exact": false) don't support filters: use an exact count. Linked namespaces (an index linked by reference) don't support filters either.

Errors

A filter that can't be read is a 422 invalid, saying which key and why:

MessageCause
filter must be a JSON objectThe filter isn't an object
filter priority: gte takes a numberA range operator with a non-number
filter user: unknown operator plan (in, eq, ne, gt, gte, lt, lte)An object value whose keys aren't operators: usually a nested object