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. The compilers are total: every filter compiles on every provider.

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}'{ 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 (metadata @> …)$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 typesmetadata->field <@ values$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)match.except, or must_notNOT COALESCE(… <@ …, false)$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.

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
  AND (jsonb_typeof((metadata->$2::text)) = 'number'
    AND (metadata->$2::text) > $3::jsonb))
-- $1 = '{"genre":"drama"}', $2 = 'year', $3 = '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. 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