Provider differences
The two filter cases where providers disagree, and the features one provider lacks.
The compilers agree on scalar fields with present values. Two cases differ across providers. If your application depends on either, add an exists clause or store a scalar field to make the intent explicit.
Negation and missing fields
ne, notIn, and not match records that lack the field on Qdrant, pgvector, Supabase, and Redis. Pinecone, Upstash, and Vectorize evaluate operators against present fields only. A record without genre does not match ne("genre", "drama") on those three but does match it on the other four.
Array-valued fields
eq("tags", "x") and isIn("tags", ["x"]) mean “contains x” on Qdrant, pgvector, Supabase, and Redis, where each element of a string list is indexed as its own tag. Pinecone supports $in on list fields and rejects $eq. Upstash compares the array itself, so neither builder matches an element. Reach an element with the accessors Upstash documents, such as eq("tags[0]", "x"). Vectorize indexes string, number, and boolean properties only, so an array-valued field is not filterable there at all. notIn and not(isIn(...)) are the same filter on these four providers. A record matches when none of its list elements is in the values.
Features one provider lacks
- Pinecone serverless indexes reject
delete({ filter }). The adapter returns{ kind: "unsupported", feature: "deleteByFilter" }. - Pinecone
createIndexneeds a deployment spec. The adapter defaults to serverless on AWS inus-east-1. PassindexSpecto change it. - pgvector
createIndexrunsCREATE EXTENSION IF NOT EXISTS vector, which needs a role that can create extensions. - Supabase needs the SQL functions in
sql/supabase.sqlinstalled once. Until they are, every verb returns{ kind: "unsupported" }naming the missing function. - Supabase
createIndexanddeleteIndexrun DDL, so they need the service role key. Theanonandauthenticatedroles get{ kind: "unauthorized" }, and the install script revokes those two functions from them. - Upstash cannot create a vector index from
@upstash/vector. A vecstore index is an Upstash namespace, andcreateIndexonly checks the dimension and metric against the index you connected to. See Upstash Vector. - Upstash normalizes scores to the range 0 to 1 and orders every metric so that higher is more similar. Qdrant, pgvector, Supabase, Pinecone, and Vectorize return the provider's raw score, where higher is better for cosine and dot and lower is better for euclidean. Redis returns a distance for every metric, where lower is closer.
- Upstash filters are a string, so the compiler rejects field names and string values it cannot write safely with an
invalid_argumenterror. - Upstash gives each query a filtering budget and can return fewer than
topKrecords when the filter is selective. The default namespace mode scopes every query by namespace, so this applies to every Upstash query.namespaceMode: "native"puts each namespace in a real Upstash namespace instead. See Upstash Vector. - Vectorize has no OR operator and no test for a missing property, so
or(...)andexists(...)return{ kind: "unsupported" }before the request is sent. See Cloudflare Vectorize. - Vectorize deletes by id only, so
delete({ filter })anddelete({ all: true })return{ kind: "unsupported" }. - Vectorize filters match nothing on a property with no metadata index, and a metadata index has to exist before you insert. Pass
metadataIndexessocreateIndexopens them. - Vectorize writes are asynchronous. An upsert returns once the mutation is enqueued, and the records become searchable a moment later.
- Redis needs the query engine and JSON, which ship with Redis 8, Redis Stack 7.4 or later, and Redis Cloud. On a server without them every verb returns
{ kind: "unsupported", feature: "queryEngine" }. - Redis searches a metadata field only when the index schema declares it, so
createRedisStoretakesmetadataFieldsand the compiler rejects a filter on any other field withinvalid_argument. A number belongs to anumericfield and everything else to atagfield, andupsertrejects a record that contradicts that, because Redis would leave the whole document out of the index. - Redis tag matching is case-sensitive, so
eq("genre", "Drama")does not matchdrama. - Redis returns the vector distance as the score, where lower is closer, for every metric. See Redis.
- Redis
listIndexesreturns every index in the database, including indexes another application created.
Metadata keys that contain a dot
Qdrant, Upstash, and Vectorize read a . in a filter field as a step into a nested object, and the adapters store metadata flat. A filter on a key that contains a dot, such as eq("file.name", "a.pdf"), therefore never sees the value. eq matches nothing on those three, and ne matches every record on Qdrant and none on Upstash and Vectorize, which evaluate operators against present fields only. pgvector and Supabase look the key up as written, and Redis rejects the field name with invalid_argument. Use metadata keys without dots for anything you filter on.