Chroma redesigned its API in 0.4 — the old Client(Settings(...))
pattern is gone, replaced by PersistentClient,
HttpClient, and CloudClient.
Telemetry is on by default — opt out with anonymized_telemetry=False.
Index type is HNSW; only cosine / L2 / IP distances are supported. This sheet pins to 0.5+.
Install · serverSetup
bash
# Python client — covers both local and server modes
pip install "chromadb>=0.5"
# Docker — single-node server (HTTP API on :8000)
docker run -p 8000:8000 -v chroma:/data \
-e IS_PERSISTENT=TRUE -e PERSIST_DIRECTORY=/data \
chromadb/chroma:latest
# CLI — run the server from the package
chroma run --path ./chroma_data --host 0.0.0.0 --port 8000
# JS / TS client
npm install chromadb chromadb-default-embed
Where things liveCommon imports
import chromadb
Core package — clients live on the top level.
from chromadb import PersistentClient, HttpClient, EphemeralClient
Three client modes — local disk, remote server, in-memory.
from chromadb.config import Settings
Tweak telemetry, allow_reset, auth.
from chromadb.utils import embedding_functions
Bundled embedders (OpenAI, ST, Cohere, ...).
from chromadb.api.types import Document, EmbeddingFunction
Types for custom embedders.
from chromadb.errors import IDAlreadyExistsError, NotFoundError
Common exceptions.
Local · server · cloudClients
chromadb.PersistentClient(path="./d")
Local disk store. Preferred for dev / single-process apps.
class MyEF(EmbeddingFunction): def __call__(self, input): ...
Custom — must map list[str] → list[list[float]].
col = get_or_create_collection("kb", embedding_function=ef)
Once attached, Chroma embeds for you on add/query.
python
from chromadb.utils import embedding_functions
# Default — onnx all-MiniLM-L6-v2, ships with the package, no API key
default_ef = embedding_functions.DefaultEmbeddingFunction()
# OpenAI
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
api_key=os.environ["OPENAI_API_KEY"],
model_name="text-embedding-3-small",
)
# Sentence-Transformers — runs locally on CPU/GPU
st_ef = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="BAAI/bge-small-en-v1.5"
)
# Custom — any callable that maps list[str] -> list[list[float]]
class MyEF(embedding_functions.EmbeddingFunction):
def __call__(self, input):
return [[0.0] * 384 for _ in input]
# Attach to a collection — once set, you don't pass embeddings yourself
col = client.get_or_create_collection("docs", embedding_function=openai_ef)
The embedding function is not stored with the collection — you must re-attach the same function (or pass embeddings explicitly) every time you re-open the client. Mismatched dimensions throw on insert.
add · upsert · update · deleteAdding data
col.add(ids, documents, metadatas)
Insert; errors on duplicate id.
col.upsert(ids, documents, metadatas)
Preferred — replaces if id exists.
col.update(ids, documents=..., metadatas=...)
Partial update; errors on missing id.
col.add(..., embeddings=[[...], [...]])
Pass vectors directly — skips the embedding function.
col.delete(ids=["a","b"])
Delete by id.
col.delete(where={"source": "stale"})
Delete by metadata filter.
metadatas=[{"k": str|int|float|bool}, ...]
Scalar metadata only — no nested objects or lists.
ids must be unique & str
UUIDs or your own hash. Used for upsert + delete.
Batch ≤ ~5000 per call
Chunk huge corpora — the HTTP server has a default body cap.
Index · query in 25 linesEnd-to-end · Minimal RAG store
Persistent client + Sentence-Transformers embedder + a filtered query. Swap in your own corpus — the shape stays.
python
# Minimal RAG store — persistent client, one collection, query with a filter
import chromadb
from chromadb.utils import embedding_functions
client = chromadb.PersistentClient(path="./chroma_data")
ef = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="BAAI/bge-small-en-v1.5"
)
col = client.get_or_create_collection(
name="kb",
embedding_function=ef,
metadata={"hnsw:space": "cosine"}, # cosine ∈ [0,2], lower is closer
)
# Index — Chroma embeds documents for you because ef is set
col.upsert(
ids = ["d1", "d2", "d3"],
documents = ["Vectors are dense embeddings.",
"RAG joins retrieval with generation.",
"Chroma is local-first."],
metadatas = [{"source": "blog"},
{"source": "docs"},
{"source": "docs"}],
)
# Retrieve top-2 docs from "docs" only
hits = col.query(
query_texts=["what is RAG?"],
n_results=2,
where={"source": "docs"},
)
for d, m, dist in zip(hits["documents"][0], hits["metadatas"][0], hits["distances"][0]):
print(f"{dist:.3f} [{m['source']}] {d}")
Best practiceGood to know
Default distance is L2, not cosine.
For text embeddings (which are usually normalized), set {"hnsw:space": "cosine"} at create time. Distances become directly interpretable: 0 = identical, 1 = orthogonal, 2 = opposite.
Use upsert instead of add by default.
Most pipelines re-index the same content multiple times during development. add errors on duplicates; upsert is idempotent and just as fast.
Re-attach the same embedding function every time.
Chroma stores vectors but not the function that produced them. Open a collection with the wrong embedder and you’ll silently get nonsense rankings — or a dimension-mismatch error on first insert.
Common trapsWatch out for
Metadata values can’t be lists or nested objects.
Only str | int | float | bool | None. Stash JSON-encoded payloads in a string column or model lists as separate boolean tags (tag_rag: True).
Telemetry is on by default.
The default client pings posthog.com. Pass Settings(anonymized_telemetry=False) in regulated environments — otherwise air-gapped deploys silently fail health checks.
PersistentClient isn’t multi-process safe.
Two Python processes opening the same path will corrupt the SQLite store. Use the HTTP server with HttpClient the moment you have more than one writer.
Chroma is an open-source vector database designed to store and query embeddings alongside their metadata. It is commonly used in LLM applications to implement semantic search, RAG (retrieval-augmented generation) pipelines, and memory stores.
What is the difference between PersistentClient and EphemeralClient in Chroma?
PersistentClient stores data on disk and survives restarts, making it the preferred choice for development and single-process apps. EphemeralClient keeps everything in memory and is discarded when the process exits — ideal for tests and notebooks.
How do metadata filters work in Chroma queries?
Chroma supports a where dict of metadata conditions, e.g. where={"source": "docs"}. You can combine conditions with $and/$or operators and use comparison operators like $gte and $lt. Filters apply before the vector similarity search, narrowing the candidate set.
Can Chroma run as a server?
Yes. Run chroma run --path ./db to start the HTTP server, then connect with chromadb.HttpClient(host="localhost", port=8000). An async variant, AsyncHttpClient, is available for non-blocking workflows.
How do I use custom embedding functions in Chroma?
Implement a class with a __call__(input: Documents) -> Embeddings method and pass it as embedding_function when creating or getting a collection. Chroma ships bundled functions for OpenAI, Sentence Transformers, Cohere, and others in chromadb.utils.embedding_functions.
Is Chroma free and open source?
Yes, Chroma is Apache-2.0 licensed. The local PersistentClient and self-hosted server are free. Chroma Cloud is a managed hosted option with its own pricing.