Skip to main content
Filter composition: AND, OR, and NOT operators nest to build complex filter logic on document payloads
Filters narrow results using logical operators to combine conditions. They operate on document payloads (metadata, enrichments, passthrough fields) and can be applied in retriever execution or as dedicated filter@v1 stages.

Payload Indexes

Filters require payload indexes on the fields you filter by. Without an index, the vector store performs a full scan — which is slow on large collections and may return incomplete results.
Create indexes on your namespace before using filters:
Supported index types: If you filter on an unindexed field, the response includes a warnings array telling you which fields need indexes:
System fields (collection_id, bucket_id, object_id, batch_id) and _internal.* fields are indexed automatically — you only need to create indexes for your own fields.

Logical Operators

Mixpeek filters support three logical operators for composing conditions:

AND Operator

Requires all nested conditions to match:

OR Operator

Matches if any nested condition is true:

NOT Operator

Excludes documents matching the condition:

Nesting Operators

Logical operators can be nested to create complex filter logic:
This filter matches documents that are:
  • Published AND
  • Either video or audio AND
  • Not restricted

Comparison Operators

Use these operators within conditions:
Use text to match any of the query tokens (BM25); use phrase when word order matters — e.g. find a transcript where someone says an exact quote. { "field": "transcription", "operator": "phrase", "value": "make america great again" } matches “…make america great again…” but not “america will be great again”.

Geospatial Operators

Geospatial operators filter documents by a location field, a payload value holding a geographic point. A point may be either an object { "lat": <num>, "lon": <num> } or a GeoJSON-style [lon, lat] array. A field holding a list of points matches if any point satisfies the predicate. See Geospatial filtering for how the same operators behave inside the attribute_filter stage. Each operator takes a structured value:
Distances use the haversine formula on a spherical earth (R = 6,371,000 m). Bounding boxes handle the antimeridian: when top_left.lon > bottom_right.lon the box is treated as wrapping across ±180°. Malformed geometry (out-of-range lat/lon, a missing corner, or fewer than 3 polygon points) is rejected at request time with a descriptive error; a document whose location field is missing or unparseable is a non-match (it is never an error).

Lineage Shortcuts

Every Mixpeek document carries a _internal.lineage block recording where it came from. To filter by lineage you don’t have to use the underscore-prefixed paths — use the friendly aliases below in any field position.
You can mix lineage aliases with regular fields and templates:
The aliases are also accepted by document list endpoints and retriever filter stages — the same vocabulary works everywhere field is used.

The _internal Envelope

Every document carries an _internal envelope that Mixpeek writes and owns. The rule for what lives there: content at the root, record-keeping in _internal. Content is what the asset is — anything you filter on, rank by, or show a user, including everything Mixpeek derived for you (embeddings, transcripts, taxonomy labels). _internal is what the row is — how it came to exist, who may read it, and the identity and timestamps of the record. You can filter on these fields anywhere field is used; system-owned ones are rejected if you try to set them on write.
Date-range filtering on _internal.created_at and _internal.updated_at is not yet supported, and it fails silently. A range filter (gte/lte/gt/lt) on either field returns an empty result set with HTTP 200 and no error — which is indistinguishable from “no documents matched your dates.” Equality on the exact stored value works. Until this lands (BACKE-3289), filter by these timestamps with eq, or filter by date at the application layer.This is not limited to the two _internal timestamps. It affects any Datetime payload index, including one you define yourself. A Datetime index is built as a numeric index keyed on a 64-bit integer, and writes pass the value through a float conversion that returns nothing for an ISO string, so the index ingests zero points. eq still works because it is served by the keyword index. Only range is dead.

Every _internal field

This table is generated from the server’s field registry (shared/databases/internal_field_registry.py), so it is the complete, authoritative set — if a name is not here, Mixpeek does not write it under _internal. Filter on the field / alias column; the storage path is shown so you can address a nested value explicitly. Identity Timestamps Lineage System metadata Blobs Provenance Tenancy Access control

Using Templates

Reference request inputs or stage outputs in filter values:

Filter Stage Example

Stage Pre-Filters and Post-Filters

Every stage accepts optional pre_filters and post_filters as siblings of parameters. Use pre_filters. They narrow the candidate set before the stage runs, pushed down into the vector store as native filters. Both take the same logical-operator shape as any other filter.
post_filters is accepted and never applied. The field is declared on every stage and passes validation, and no stage applies it. A post_filters predicate returns the same documents as sending no filter at all, with HTTP 200 and no warning.Measured on feature_search: a nonsense value returns the full unfiltered set, identical to an unfiltered run. The identical predicate in pre_filters filters correctly.Put any predicate you rely on in pre_filters. Never use post_filters to restrict scope, because it does not restrict anything.
Canonical shape — wrap conditions in an explicit logical operator:
Always prefer the explicit { "AND": [ ... ] } form — it is unambiguous and nests cleanly with OR/NOT.
For convenience, two shorthand forms are coerced to an AND group:
  • a single bare condition{ "field": "...", "operator": "...", "value": "..." } becomes { "AND": [ <condition> ] }
  • a list of conditions[ { ... }, { ... } ] becomes { "AND": [ ... ] }
Each condition must carry all three of field, operator, and value. An incomplete condition (for example, a missing operator) is rejected with a clear error rather than silently ignored. That guarantee covers the shape of a condition, not where you place it. A well-formed condition placed in post_filters still degrades into an unfiltered result, as described above.

Options