Move between providers
How to move a running application from one adapter to another without rewriting queries.
The verbs, filters, and result types stay the same when you move. What changes is the client you construct, how each provider stores ids and namespaces, and two filter edge cases.
Swap the adapter
Replace the client and the create*Store import. Nothing else in your code refers to the provider.
import { Pinecone } from "@pinecone-database/pinecone";
import { createPineconeStore } from "vecstore-sdk/pinecone";
const store = createPineconeStore({ client: new Pinecone({ apiKey }) });import { QdrantClient } from "@qdrant/js-client-rest";
import { createQdrantStore } from "vecstore-sdk/qdrant";
const store = createQdrantStore({ client: new QdrantClient({ url }) });import { Pool } from "pg";
import { createPgvectorStore } from "vecstore-sdk/pgvector";
const store = createPgvectorStore({ client: new Pool({ connectionString }) });import { createClient } from "@supabase/supabase-js";
import { createSupabaseStore } from "vecstore-sdk/supabase";
const store = createSupabaseStore({ client: createClient(url, key) });import { Index } from "@upstash/vector";
import { createUpstashStore } from "vecstore-sdk/upstash";
const store = createUpstashStore({ client: new Index({ url, token }) });import Cloudflare from "cloudflare";
import { createVectorizeStore } from "vecstore-sdk/vectorize";
const store = createVectorizeStore({
client: new Cloudflare({ apiToken }),
accountId,
});import { createClient } from "redis";
import { createRedisStore } from "vecstore-sdk/redis";
const store = createRedisStore({
client,
metadataFields: [{ field: "genre", type: "tag" }],
});Moving to Supabase needs a setup step the others do not: install sql/supabase.sql in the project before the first call. The Supabase page covers it.
Copy the data
The SDK does not move records for you. Read from the old index with fetch or your own export, then upsert into the new one. The adapter batches upserts, so pass as many records per call as fit in memory.
Record ids, vectors, and metadata round-trip unchanged. Every provider accepts only string, number, boolean, and string[] metadata values, so a record that upserts on one provider upserts on all of them.
Check what each provider stores
| Concern | Qdrant | pgvector and Supabase | Pinecone | Upstash | Vectorize | Redis |
|---|---|---|---|---|---|---|
| Index | Collection | Table | Index | Namespace | Index | Index over a key prefix |
| Namespace | _namespace payload key | namespace column | Native | _namespace metadata key, or a real namespace under namespaceMode: "native" | Native | The namespace tag, and a segment in the key |
| Id | UUID, hashed from your id when needed. The original id is kept in _id. | id text | Native | Prefixed with the namespace. The original id is kept in _id. Unchanged under namespaceMode: "native". | UUID, hashed from your id when needed. The original id is kept in _id. | The last segment of the key |
| Metadata | Payload | metadata jsonb | Metadata | Metadata | Metadata | The metadata object in the JSON document |
| Metric | Set on the collection | Read from the HNSW index operator class | Set on the index | Set on the Upstash index | Set on the index | Set on the vector field |
The pgvector and Supabase adapters share a table layout, so moving between those two copies no data. Point the other adapter at the same table.
If you read a Qdrant collection, a pgvector table, an Upstash namespace, or a Vectorize index with another tool, expect those extra keys and columns.
Moving to Upstash needs one step the others do not. Create the vector index in the Upstash console first with the dimension and similarity function you want, because @upstash/vector cannot create one. createIndex then checks your spec against it.
Moving to Vectorize needs one too. Name the properties you filter on in metadataIndexes before the first upsert, because a Vectorize filter matches nothing on a property that has no metadata index, and an index added later does not cover data already written. Vectorize also has no OR operator, no exists, and no delete beyond delete-by-id. See Cloudflare Vectorize.
Moving to Redis needs the same kind of step. Name the fields you filter on in metadataFields, because the Redis index schema decides what a query can match on. Unlike Vectorize, adding a field later costs only a deleteIndex and a createIndex: the documents stay where they are and Redis indexes them again. The server also has to carry the query engine and JSON. See Redis.
Re-check score thresholds
Qdrant, pgvector, Pinecone, and Vectorize return the provider's native score for the index metric, Upstash normalizes every metric to the range 0 to 1, and Redis returns a distance where lower is closer. A threshold tuned on one provider does not carry over. See the score table in Stores and indexes and check the threshold against real data.
Check the filter edge cases
Negation on missing fields and eq on array-valued fields behave differently per provider. See Provider differences. If your queries depend on either case, add an exists clause or store a scalar field before you move.
Verify the move
Before you cut over, run the live conformance suite against the new backend:
VECSTORE_LIVE=1 QDRANT_URL=http://localhost:6333 bun run test:liveThe suite creates a temporary index, exercises every verb and filter operator, and deletes the index.