quiver admin import loads an export from another vector database — Qdrant,
Chroma, or pgvector — into a Quiver collection, preserving ids, vectors,
payloads, and (optionally) the filterable fields hybrid search needs. The design
is recorded in ADR-0024.
The importer can either read a file you export from the source tool or pull
directly from a running source — Qdrant, Chroma, or Postgres
(ADR-0027,
ADR-0029). Either way it
bulk-loads into a local data directory through the engine — so the result is an
ordinary Quiver store: crash-safe, encrypted at rest (unless --insecure), and
immediately serveable with quiver serve.
Qdrant — scroll the collection to JSON Lines (one point per line). Using the Python client:
from qdrant_client import QdrantClient
import json
client = QdrantClient(url="http://localhost:6333")
with open("qdrant.jsonl", "w") as f:
offset = None
while True:
points, offset = client.scroll(
"my_collection", with_vectors=True, with_payload=True,
limit=1000, offset=offset,
)
for p in points:
f.write(json.dumps({"id": p.id, "vector": p.vector, "payload": p.payload}) + "\n")
if offset is None:
breakChroma — dump the collection's get(...) result as one JSON object:
import chromadb, json
col = chromadb.PersistentClient("./chroma").get_collection("my_collection")
data = col.get(include=["embeddings", "metadatas", "documents"])
json.dump(data, open("chroma.json", "w"))pgvector — emit one JSON row per line with row_to_json (the embedding
column comes out as a "[1,2,3]" text literal, which the importer parses):
psql "$DATABASE_URL" -At -c \
"SELECT row_to_json(t) FROM (SELECT id, embedding, title, category FROM items) t" \
> pgvector.jsonl# Qdrant → an encrypted local store (dimension inferred from the export)
export QUIVER_ENCRYPTION_KEY=<64-hex-character master key>
quiver admin import --source qdrant --input qdrant.jsonl \
--collection my_collection --data-dir ./data --metric cosine
# Chroma, declaring filterable payload fields for hybrid search
quiver admin import --source chroma --input chroma.json \
--collection docs --data-dir ./data --metric cosine \
--filterable category:keyword --filterable year:numeric
# pgvector, naming the id/vector columns, no encryption (dev only)
quiver admin import --source pgvector --input pgvector.jsonl \
--collection items --data-dir ./data --metric l2 \
--id-field id --vector-field embedding --insecureFor a live import — no export step — point at a running source instead of
--input. All three reuse the same normalization and write path as the offline
importer (ADR-0027,
ADR-0029):
# Qdrant — paginated points/scroll; --collection is the source collection name
quiver admin import --source qdrant --qdrant-url http://localhost:6333 \
--collection my_collection --data-dir ./data --metric cosine
# add --api-key <key> (or set QDRANT_API_KEY) for a secured Qdrant
# Chroma — v2 HTTP API; resolves the collection name to its id, then paginates get
quiver admin import --source chroma --chroma-url http://localhost:8000 \
--collection docs --data-dir ./data --metric cosine
# override --chroma-tenant / --chroma-database for a non-default deployment;
# add --api-key <token> for a secured Chroma (sent as x-chroma-token)
# Postgres/pgvector — reads row_to_json over the table; TLS per the URL's sslmode
quiver admin import --source pgvector \
--postgres-url postgresql://user:pass@localhost/db \
--table items --collection items --data-dir ./data --metric l2
# --table defaults to --collection; use sslmode=disable for a plaintext/local DBLive connectors are validated against a hermetic in-process server (Qdrant, Chroma) or the offline mapper plus an opt-in integration test (Postgres); validating against your running instance is the final step on your side.
Then serve it with the same key (the importer writes the same encrypted format the server reads):
QUIVER_ENCRYPTION_KEY=<same key> quiver serve # data_dir defaults to ./data| Flag | Meaning | Default |
|---|---|---|
--source |
qdrant, chroma, or pgvector |
required |
--input |
export file for an offline import (JSON Lines for qdrant/pgvector; one JSON object for chroma) | one of --input / a live --*-url |
--qdrant-url |
base URL of a running Qdrant for a live import (qdrant only) | one of --input / a live --*-url |
--chroma-url |
base URL of a running Chroma for a live import (chroma only) | — |
--chroma-tenant / --chroma-database |
Chroma tenant / database for --chroma-url |
default_tenant / default_database |
--postgres-url |
connection URL of a running Postgres for a live import (pgvector only) | — |
--table |
source table for --postgres-url |
--collection |
--api-key |
API key for a live import: Qdrant api-key / Chroma x-chroma-token (or QDRANT_API_KEY) |
— |
--collection |
target collection (created if absent, appended to otherwise); also the source collection name for a live import | required |
--data-dir |
target data directory | ./data |
--metric |
l2, cosine, or dot (for a newly created collection) |
cosine |
--dim |
vector dimensionality | inferred from the export |
--filterable |
path:type (keyword|numeric), repeatable |
none |
--id-field |
id column name (pgvector) | id |
--vector-field |
vector column name | vector (qdrant) / embedding (pgvector) |
--vector-name |
which named vector to import (qdrant) | the sole one |
--encryption-key |
64-hex master key (or QUIVER_ENCRYPTION_KEY) |
— |
--insecure |
import without encryption-at-rest (dev only) | off |
- Ids are stringified (Qdrant/Chroma integer or UUID ids become strings).
- Payloads: Qdrant
payloadis kept as-is; for pgvector every non-id, non-vector column becomes a payload field; for Chroma themetadatasobject is the payload and eachdocumentsentry is stored under adocumentkey. - Filterable fields must be declared at import time to be usable by hybrid search later (they are extracted into the secondary index at flush — ADR-0022).
- Importing the same export twice appends (re-upserting the same ids replaces them); the importer never drops an existing collection.
- Live import is available for all three sources — Qdrant (
--qdrant-url, ADR-0027), Chroma (--chroma-url) and Postgres (--postgres-url, ADR-0029) — each pulling directly from a running instance through the same normalization as the offline path. Live Chroma uses its v2 HTTP API (resolving the collection name to an id by listing collections); live Postgres readsrow_to_jsonover the table and negotiates TLS per the URL'ssslmode.
quiver admin import is an operator command: the source URL you pass is
trusted input you chose, not a request an attacker can influence, so fetching it
is not server-side request forgery (see finding C1 in the
v0.17.0 audit note). Two operational cautions still
apply, and the CLI warns about both:
- Use TLS for credentials. A Qdrant/Chroma API key over a plaintext
http://URL, or a Postgres password withsslmode=disable, travels in cleartext to anyone on the path. Preferhttps://(Qdrant/Chroma) andsslmode=requireor stronger (Postgres). The importer prints awarning:to stderr when it detects a credential that would be sent unencrypted. - The SQL table name is interpolated as a quoted identifier (each
dot-separated part is double-quoted with embedded quotes doubled), so a crafted
--tablecannot break out of theSELECT.