Skip to content

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

BuilderQdrantpgvector and SupabasePineconeUpstashVectorizeRedis
eq(field, value)match.value, or a closed range for floatsmetadata @> '{"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 aboveNOT (…) of the above$nefield != value$ne-(…) of the above
gt, gte, lt, lterangeA 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 typesan OR of the eq form per value$in, or $or of $eq for booleansfield 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 conditionsNOT (…) of the above$nin, or $and of $ne for booleansfield NOT IN (…)$nin-(…) of the above
exists(field)must_not is_emptyIS NOT NULL AND jsonb_typeof <> 'null'$exists: trueHAS FIELD fieldunsupported-ismissing(@field)
and, ormust, shouldAND, OR$and, $orAND, ORand merges fields into one object, or is unsupporteda space, |
not(filter)must_notNOT (…)Pushed to the leaves with De Morgan's lawsPushed to the leaves with De Morgan's lawsPushed 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:

Qdrant
{
  "must": [
    { "key": "genre", "match": { "value": "drama" } },
    { "key": "year", "range": { "gt": 2000 } }
  ]
}
pgvector and Supabase
((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'
Pinecone
{
  "$and": [{ "genre": { "$eq": "drama" } }, { "year": { "$gt": 2000 } }]
}
Upstash
(genre = 'drama' AND year > 2000)
Vectorize
{
  "genre": { "$eq": "drama" },
  "year": { "$gt": 2000 }
}
Redis
(@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.

On this page