Filters
Reference for the filter builders and the native syntax each adapter compiles them to.
You build a filter once with the exported helpers. Each adapter compiles the same filter to its provider's native syntax. Five compilers accept every filter. The Vectorize and Redis compilers return a Result, because Vectorize has no or and no exists, and Redis matches only fields its index schema declares.
import { and, eq, gt, isIn, not, or } from "vecstore-sdk";
const filter = and(
eq("genre", "drama"),
gt("year", 2000),
or(isIn("lang", ["en", "th"]), not(eq("region", "eu")))
);Builders
| Builder | Qdrant | pgvector and Supabase | Pinecone | Upstash | Vectorize | Redis |
|---|---|---|---|---|---|---|
eq(field, value) | match.value, or a closed range for floats | metadata @> '{"field": value}' OR metadata @> '{"field": [value]}' | { field: { $eq } } | field = value | { field: { $eq } } | @field:{"value"} on a tag, @field:[v v] on a number |
ne(field, value) | must_not of the above | NOT (…) of the above | $ne | field != value | $ne | -(…) of the above |
gt, gte, lt, lte | range | A jsonb comparison guarded by jsonb_typeof | $gt, $gte, $lt, $lte | >, >=, <, <= | $gt, $gte, $lt, $lte | @field:[(v +inf] and the other three bounds |
isIn(field, values) | match.any, or should for mixed types | an OR of the eq form per value | $in, or $or of $eq for booleans | field IN (…) | $in | @field:{"a" | "b"} on a tag, a union of ranges on a number |
notIn(field, values) | must_not of match.any, or of the per-value conditions | NOT (…) of the above | $nin, or $and of $ne for booleans | field NOT IN (…) | $nin | -(…) of the above |
exists(field) | must_not is_empty | IS NOT NULL AND jsonb_typeof <> 'null' | $exists: true | HAS FIELD field | unsupported | -ismissing(@field) |
and, or | must, should | AND, OR | $and, $or | AND, OR | and merges fields into one object, or is unsupported | a space, | |
not(filter) | must_not | NOT (…) | Pushed to the leaves with De Morgan's laws | Pushed to the leaves with De Morgan's laws | Pushed to the leaves with De Morgan's laws | -(…) around the clause |
isIn, notIn, and, and or take at least one element. The types enforce this.
Every adapter checks a filter before it sends anything. A value has to be a string, a finite number, or a boolean, a range bound has to be a finite number, and a list or a combinator cannot be empty. NaN, Infinity, and a value that reached the builder as undefined return invalid_argument.
Without the check, JSON.stringify turns those values into null or drops them, and a provider reads the result as a wider filter, up to one that matches every record in the namespace. The exported compilers skip this check, so validate values yourself when you call one directly.
Compiled output
The filter and(eq("genre", "drama"), gt("year", 2000)) compiles to:
{
"must": [
{ "key": "genre", "match": { "value": "drama" } },
{ "key": "year", "range": { "gt": 2000 } }
]
}((metadata @> $1::jsonb OR metadata @> $2::jsonb)
AND ((metadata->$3::text) IS NOT NULL
AND jsonb_typeof((metadata->$3::text)) = 'number'
AND (metadata->$3::text) > $4::jsonb))
-- $1 = '{"genre":"drama"}', $2 = '{"genre":["drama"]}', $3 = 'year', $4 = '2000'{
"$and": [{ "genre": { "$eq": "drama" } }, { "year": { "$gt": 2000 } }]
}(genre = 'drama' AND year > 2000){
"genre": { "$eq": "drama" },
"year": { "$gt": 2000 }
}(@genre:{"drama"} @year:[(2000 +inf])Inspect the native filter
Qdrant, pgvector, Pinecone, Upstash, Vectorize, and Redis each export their compiler. Import it to log or reuse the native output:
import { compileQdrantFilter } from "vecstore-sdk/qdrant";
const native = compileQdrantFilter(filter);Supabase has no compiler to import. The filter travels to Postgres as JSON and vecstore_filter_sql emits the pgvector predicates there, as the Supabase page describes.
compileRedisFilter takes a second argument, the metadata fields the index schema declares, because a Redis query names a field the schema has to hold.
Where a provider lacks an operator, the compiler rewrites the filter instead of failing. Three providers are exceptions. Upstash's filter is a string, so the compiler rejects a field name or a string value it cannot write safely and the verb returns an invalid_argument error. Vectorize has no OR and no presence test, so compileVectorizeFilter returns a Result and the verb returns an unsupported error before it sends a request, or invalid_argument when two clauses set the same operator on one field, or mix a membership and a comparison operator on one field. Redis matches nothing on a field its schema does not declare, so compileRedisFilter returns a Result and the verb returns an invalid_argument error instead of an empty page. See Provider differences for the two cases where the semantics diverge.