Skip to content

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

ConcernQdrantpgvector and SupabasePineconeUpstashVectorizeRedis
IndexCollectionTableIndexNamespaceIndexIndex over a key prefix
Namespace_namespace payload keynamespace columnNative_namespace metadata key, or a real namespace under namespaceMode: "native"NativeThe namespace tag, and a segment in the key
IdUUID, hashed from your id when needed. The original id is kept in _id.id textNativePrefixed 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
MetadataPayloadmetadata jsonbMetadataMetadataMetadataThe metadata object in the JSON document
MetricSet on the collectionRead from the HNSW index operator classSet on the indexSet on the Upstash indexSet on the indexSet 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:live

The suite creates a temporary index, exercises every verb and filter operator, and deletes the index.

On this page