Clients
Install with . Every example on this page uses a client named and a collection named .
| Task | Code |
|---|---|
| Local database on disk | |
| In-memory database for tests | |
| Connect to a Chroma server | |
| Async client for a server | |
| Chroma Cloud | |
| Turn off telemetry in code | |
| Turn off telemetry with an environment variable | |
| Use another tenant or database | |
| Switch database on an open client | |
| Check the server is up | |
| Client package version | |
| Largest batch for add, upsert and delete | |
| Wipe every collection (needs ) | Destructive |
| JavaScript client (server only, no embedded mode) |
comes from . The JavaScript client () uses camelCase for the same calls: , , , and it needs installed for the default embedding model.
Example: open a local database with telemetry off and confirm it responds.
import chromadb
from chromadb.config import Settings
client = chromadb.PersistentClient(
path="./chroma",
settings=Settings(anonymized_telemetry=False),
)
print(client.heartbeat(), chromadb.__version__)
Collections
A collection is Chroma's table: records with an ID, a document, metadata and an embedding.
| Task | Code |
|---|---|
| Create (SQL ) | |
| Create, or open if it exists | |
| Open an existing collection | |
| Use cosine distance (set at creation) | |
| List collection names | |
| Page through collections | |
| Number of collections | |
| Number of records (SQL ) | |
| First few records | |
| Show distance space and HNSW settings | |
| Rename | |
| Replace collection metadata | |
| Raise search accuracy at query time | |
| Delete (SQL ) | Destructive |
The distance space is unless you pass or , and it cannot change later. Names need 3 to 512 characters from . raises for a missing name, and raises an "already exists" error for a taken one.
Example: create the collection used on the rest of this page, with cosine distance.
docs = client.get_or_create_collection(
name="support_docs",
configuration={"hnsw": {"space": "cosine"}},
metadata={"team": "support"},
)
print(docs.configuration["hnsw"])
In 1bench: the Chroma GUI has a schema view per collection that shows the tenant and database, the embedding function, the distance space and every HNSW setting, so you do not have to print to check them.
Add and Upsert
| Task | Code |
|---|---|
| Add records, embedded by the collection (SQL ) | |
| Add with your own vectors | |
| Insert or replace (SQL upsert, ) | |
| Metadata value types | , , , , or a list of one of these |
| List metadata for tags |
skips an ID that already exists and raises no error, so the old record stays. Use when a record may already be there.
Example: load four support articles with source, category and year metadata.
docs.add(
ids=["doc1", "doc2", "doc3", "doc4"],
documents=[
"How to reset your password from the login page",
"Refunds are issued within 5 business days",
"Enable two-factor authentication in account settings",
"Export your invoices as PDF from the billing page",
],
metadatas=[
{"source": "help_center", "category": "account", "year": 2025},
{"source": "faq", "category": "billing", "year": 2024},
{"source": "help_center", "category": "security", "year": 2026},
{"source": "faq", "category": "billing", "year": 2026, "tags": ["pdf", "invoice"]},
],
)
print(docs.count())
A call with more records than (5461 in our test) fails with "Batch size ... is greater than max batch size". Split big loads:
batch = client.get_max_batch_size()
for i in range(0, len(ids), batch):
docs.upsert(
ids=ids[i:i + batch],
documents=texts[i:i + batch],
metadatas=metas[i:i + batch],
)
Get vs Query
reads records by ID or filter with no ranking. runs a similarity search and returns the nearest records first. Both take , and .
| Task | Code |
|---|---|
| Fetch by ID (SQL ) | |
| Get all documents | |
| Only the IDs | |
| Page with and | |
| Choose returned fields | |
| Similarity search by text | |
| Similarity search by vector | |
| Several searches in one call | |
| Search only within some IDs | |
| Return distances (query only) |
Defaults: returns metadatas and documents, adds distances. with raises an error. returns one list per query text, so read , not . Asking for more than there are records returns them all.
Example: the two closest articles to a user question, with their distances.
r = docs.query(
query_texts=["I forgot my password"],
n_results=2,
include=["documents", "metadatas", "distances"],
)
for doc_id, text, dist in zip(r["ids"][0], r["documents"][0], r["distances"][0]):
print(doc_id, round(dist, 3), text)
Metadata Filters
filters on metadata in both and . Each dict holds exactly one field or one operator.
| Task | Filter |
|---|---|
| Equal (SQL ) | |
| Equal, explicit form | |
| Not equal (SQL ) | |
| Greater than | |
| Greater than or equal | |
| Less than | |
| Less than or equal | |
| Any of several values (SQL ) | |
| None of several values (SQL ) | |
| Both conditions (SQL ) | |
| Either condition (SQL ) | |
| List metadata contains a value | |
| List metadata lacks a value |
Two keys in one dict, such as , raise "Expected where to have exactly one operator". Wrap them in . Comparison operators check types too: fails because the value is a string.
Example: FAQ billing articles, nearest to a refund question.
r = docs.query(
query_texts=["how long does a refund take"],
n_results=3,
where={"$and": [{"source": "faq"}, {"category": "billing"}]},
)
print(r["ids"][0])
In 1bench: the Chroma GUI has a query builder with Query and Get modes, an IDs box, a where JSON field and a where_document field with a negate switch, and it shows the request body it will send.
Document Filters
filters on the document text. Use it alone with , or next to in a .
| Task | Filter |
|---|---|
| Text contains (SQL ) | |
| Text does not contain (SQL ) | |
| Regular expression | |
| Case-insensitive regex | |
| Does not match a regex | |
| Either phrase (SQL ) | |
| Both phrases (SQL ) |
is case-sensitive: does not match "password". Use with when case should not matter.
Example: billing articles that mention PDF, ranked against an invoice question.
r = docs.query(
query_texts=["download my invoice"],
n_results=2,
where={"category": "billing"},
where_document={"$contains": "PDF"},
include=["documents", "distances"],
)
print(r["documents"][0], r["distances"][0])
Update and Delete
| Task | Code |
|---|---|
| Change metadata fields (merges with existing keys) | |
| Remove one metadata key | |
| Change document text (re-embeds it) | |
| Delete by ID (SQL ) | Destructive |
| Delete by metadata filter | Destructive |
| Delete by document text | Destructive |
on an ID that does not exist raises no error and changes nothing. with no arguments raises an error instead of emptying the collection.
Example: retire every pre-2025 FAQ article and bump the year on one that was reviewed.
docs.delete(where={"$and": [{"source": "faq"}, {"year": {"$lt": 2025}}]})
docs.update(ids=["doc1"], metadatas=[{"year": 2026}])
print(docs.count(), docs.get(ids=["doc1"])["metadatas"])
In 1bench: the Chroma GUI exports a whole collection to a JSON file, with or without embeddings, so you can keep a copy before a bulk delete.
Embedding Functions
A collection embeds and with its embedding function. Pass one at creation; Chroma saves the choice in the collection configuration.
| Task | Code |
|---|---|
| Import the built-in functions | |
| Default model (all-MiniLM-L6-v2, ONNX, 384 dimensions) | |
| OpenAI () | |
| sentence-transformers () | |
| Ollama (, Ollama running) | |
| Attach to a new collection | |
| Embed text yourself | |
| See which one a collection uses |
The default model downloads about 80 MB to on first use. The OpenAI function reads its key from unless you set , and only the variable name is saved, never the key. restores a built-in function from the saved configuration, and passing a different one raises "Embedding function conflict".
Example: a collection embedded with OpenAI, reopened later without passing the function again.
from chromadb.utils import embedding_functions
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
api_key_env_var="OPENAI_API_KEY",
model_name="text-embedding-3-small",
)
kb = client.get_or_create_collection("support_docs_openai", embedding_function=openai_ef)
kb.add(ids=["doc1"], documents=["How to reset your password from the login page"])
kb = client.get_collection("support_docs_openai")
print(kb.query(query_texts=["lost password"], n_results=1)["ids"])
Full-Text and Hybrid Search
| Task | Code |
|---|---|
| Keyword or phrase filter, any deployment | |
| Pattern filter, any deployment | |
| Vector search narrowed by keywords | |
| BM25 sparse index (Chroma Cloud only) | with |
| Ranked search expression (Chroma Cloud only) | |
| Fuse dense and sparse rankings (Chroma Cloud only) | inside the Search API |
In chromadb 1.5.9, a local or self-hosted server rejects sparse indexes and . The Search API below runs on Chroma Cloud:
from chromadb import Search, K, Knn
s = (
Search()
.where(K("category") == "billing")
.rank(Knn(query="get my money back"))
.limit(2)
.select(K.DOCUMENT, K.SCORE, "year")
)
results = docs.search(s)
Example: a local stand-in for hybrid search, ranking by meaning and keeping only articles that mention a password.
r = docs.query(
query_texts=["I cannot log in"],
n_results=3,
where_document={"$regex": "(?i)password"},
)
print(r["ids"][0], r["distances"][0])
CLI, Server and REST API
The command ships with the Python package.
| Task | Command |
|---|---|
| Start a local server (default port 8000) | |
| Pick the port | |
| Listen on all interfaces | |
| Run in Docker with a data volume | |
| Shrink the SQLite file of a local database | |
| Browse a collection in the terminal | |
| Copy local collections to Chroma Cloud | |
| Health check | |
| List collections | |
| Interactive API docs | open |
REST calls under take the collection UUID, not its name, for , , , , , and . The server does not embed text, so a REST needs .
Example: start a server on port 8000 and fetch billing articles over REST.
chroma run --path ./chroma-data --port 8000
B=http://localhost:8000/api/v2/tenants/default_tenant/databases/default_database
ID=$(curl -s "$B/collections/support_docs" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
curl -s -X POST "$B/collections/$ID/get" \
-H 'Content-Type: application/json' \
-d '{"where": {"category": "billing"}, "include": ["documents", "metadatas"]}'
Gotchas
- with an existing ID keeps the old record and raises no error, and on a missing ID does nothing silently. Use when you are not sure the record exists.
- A dict with two fields raises "Expected where to have exactly one operator". Write , not .
- on an existing collection ignores the you pass, so a collection first created with stays . The space cannot be changed later; create a new collection and copy the records.
- , and fail above records per call. Split large loads into batches.
- is case-sensitive. Use with for a case-insensitive match.
- Embeddings come back as a NumPy array, so raises "truth value of an array ... is ambiguous". Check or instead.