Chroma — Open-Source Embedding Database
The AI-native database for building LLM applications with memory. Simple 4-function API: create_collection, add, query, get. Scales from notebooks to production clusters.
When to Use
Use Chroma when:
- Building RAG (retrieval-augmented generation) applications
- Need local or self-hosted vector database
- Want open-source solution (Apache 2.0)
- Prototyping in notebooks with semantic search
- Storing embeddings with metadata and filtering by that metadata
- Need document retrieval with vector + full-text search
Use alternatives instead:
- Pinecone: Managed cloud, auto-scaling, no infrastructure management
- FAISS: Pure similarity search, no metadata support
- Weaviate: Production ML-native database with GraphQL API
- Qdrant: High performance, Rust-based, production filtering
Metrics:
- 24,300+ GitHub stars, 1,900+ forks
- v1.3.3+ (stable, weekly releases)
- Apache 2.0 license
Prerequisites
- Python 3.8+ (or Node.js 18+ for JS/TS client)
- Install Chroma and default embedding dependencies:
# Python
pip install chromadb
# JavaScript/TypeScript
npm install chromadb @chroma-core/default-embed
- For LangChain integration:
pip install langchain-chroma langchain-openai - For LlamaIndex integration:
pip install llama-index-vector-stores-chroma - For OpenAI embeddings:
pip install openaiand setOPENAI_API_KEYenvironment variable - For HuggingFace embeddings:
pip install huggingface_huband setHF_TOKENenvironment variable
Procedure
1. Create a Client and Collection
import chromadb
# In-memory client (data lost on restart)
client = chromadb.Client()
# Persistent client (data saved to disk)
client = chromadb.PersistentClient(path="./chroma_db")
# Create a collection
collection = client.create_collection(name="my_collection")
# Get existing collection
collection = client.get_collection("my_docs")
# Get or create (safe if collection may not exist)
collection = client.get_or_create_collection("my_docs")
# Delete collection
client.delete_collection("my_docs")
2. Add Documents
# Add documents with auto-generated embeddings (default: all-MiniLM-L6-v2)
collection.add(
documents=["Doc 1", "Doc 2", "Doc 3"],
metadatas=[
{"source": "web", "category": "tutorial"},
{"source": "pdf", "page": 5},
{"source": "api", "timestamp": "2025-01-01"}
],
ids=["id1", "id2", "id3"]
)
# Add with custom pre-computed embeddings
collection.add(
embeddings=[[0.1, 0.2, 0.3], [0.3, 0.4, 0.5]],
documents=["Doc 1", "Doc 2"],
ids=["id1", "id2"]
)
3. Query (Similarity Search)
# Basic query
results = collection.query(
query_texts=["machine learning tutorial"],
n_results=5
)
# Query with metadata filter (exact match)
results = collection.query(
query_texts=["Python programming"],
n_results=3,
where={"source": "web"}
)
# Query with comparison operators
results = collection.query(
query_texts=["advanced topics"],
n_results=3,
where={"page": {"$gt": 10}} # Operators: $gt, $gte, $lt, $lte, $ne
)
# Query with logical operators
results = collection.query(
query_texts=["advanced topics"],
n_results=3,
where={
"$and": [
{"category": "tutorial"},
{"difficulty": {"$gte": 3}}
]
} # Also supports: $or
)
# Query with $in (contains)
results = collection.query(
query_texts=["query"],
where={"tags": {"$in": ["python", "ml"]}}
)
# Access results
print(results["documents"]) # List of matching documents
print(results["metadatas"]) # Metadata for each doc
print(results["distances"]) # Similarity scores
print(results["ids"]) # Document IDs
4. Get Documents (Without Query)
# Get by IDs
docs = collection.get(ids=["id1", "id2"])
# Get with filters
docs = collection.get(
where={"category": "tutorial"},
limit=10
)
# Get all documents
docs = collection.get()
5. Update Documents
# Update document content and metadata
collection.update(
ids=["id1"],
documents=["Updated content"],
metadatas=[{"source": "updated"}]
)
6. Delete Documents
# Delete by IDs
collection.delete(ids=["id1", "id2"])
# Delete with filter
collection.delete(where={"source": "outdated"})
7. Persistent Storage
# Persist to disk — data saved automatically
client = chromadb.PersistentClient(path="./chroma_db")
collection = client.create_collection("my_docs")
collection.add(documents=["Doc 1"], ids=["id1"])
# Reload later with same path
client = chromadb.PersistentClient(path="./chroma_db")
collection = client.get_collection("my_docs")
8. Custom Embedding Functions
Default (Sentence Transformers — no API key needed):
# Uses sentence-transformers all-MiniLM-L6-v2 by default
collection = client.create_collection("my_docs")
OpenAI:
from chromadb.utils import embedding_functions
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
api_key="YOUR_KEY",
model_name="text-embedding-3-small"
)
collection = client.create_collection(
name="openai_docs",
embedding_function=openai_ef
)
HuggingFace:
huggingface_ef = embedding_functions.HuggingFaceEmbeddingFunction(
api_key="YOUR_KEY",
model_name="sentence-transformers/all-mpnet-base-v2"
)
collection = client.create_collection(
name="hf_docs",
embedding_function=huggingface_ef
)
Custom embedding function:
from chromadb import Documents, EmbeddingFunction, Embeddings
class MyEmbeddingFunction(EmbeddingFunction):
def __call__(self, input: Documents) -> Embeddings:
# Your embedding logic here
return embeddings
my_ef = MyEmbeddingFunction()
collection = client.create_collection(
name="custom_docs",
embedding_function=my_ef
)
9. Server Mode (Production)
# Start Chroma server (terminal)
chroma run --path ./chroma_db --port 8000
# Connect to running server
import chromadb
from chromadb.config import Settings
client = chromadb.HttpClient(
host="localhost",
port=8000,
settings=Settings(anonymized_telemetry=False)
)
# Use as normal
collection = client.get_or_create_collection("my_docs")
10. LangChain Integration
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Split documents
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000)
docs = text_splitter.split_documents(documents)
# Create Chroma vector store
vectorstore = Chroma.from_documents(
documents=docs,
embedding=OpenAIEmbeddings(),
persist_directory="./chroma_db"
)
# Query
results = vectorstore.similarity_search("machine learning", k=3)
# As retriever
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
11. LlamaIndex Integration
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.core import VectorStoreIndex, StorageContext
import chromadb
# Initialize Chroma
db = chromadb.PersistentClient(path="./chroma_db")
collection = db.get_or_create_collection("my_collection")
# Create vector store
vector_store = ChromaVectorStore(chroma_collection=collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
# Create index
index = VectorStoreIndex.from_documents(
documents,
storage_context=storage_context
)
# Query
query_engine = index.as_query_engine()
response = query_engine.query("What is machine learning?")
Pitfalls
- In-memory client loses data on restart — Always use
PersistentClient(path=...)for any data you need to keep. The defaultchromadb.Client()is in-memory only. - Embedding function mismatch — You cannot query a collection with a different embedding function than the one used to create it. Embeddings from different models are not comparable.
- Duplicate IDs silently overwrite — Adding documents with existing IDs will overwrite the previous content without warning. Use unique IDs to avoid data loss.
- Metadata values must be primitives — Chroma metadata supports
str,int,float, andboolvalues only. Lists, dicts, or nested objects are not supported as metadata values. $inoperator requires list values — Thewhere_documentfilter does not support$in; onlywheremetadata filters support$inwith a list of values.- Server mode port conflicts — Ensure port 8000 (or your chosen port) is free before starting
chroma run. Check withnetstat -ano | findstr :8000on Windows orlsof -i :8000on Linux/macOS. - Telemetry enabled by default — Set
anonymized_telemetry=Falsein Settings to disable telemetry if required. - Large batch adds can timeout — For collections with 10,000+ documents, add in batches of 1,000-5,000 to avoid memory issues.
- No built-in authentication in server mode — Chroma server does not include authentication. Do not expose it to the public internet without a reverse proxy with auth.
- Collection name restrictions — Collection names must be 3-63 characters, start/end with alphanumeric, and contain only alphanumeric, underscores, or hyphens.
Verification
- Verify installation:
python -c "import chromadb; print(chromadb.__version__)"
# Expected output: 1.3.3 or higher
- Verify basic CRUD operations:
import chromadb
client = chromadb.Client()
collection = client.create_collection("test_verify")
collection.add(
documents=["test document"],
metadatas=[{"source": "verify"}],
ids=["id1"]
)
results = collection.query(query_texts=["test"], n_results=1)
assert results["ids"][0][0] == "id1"
assert results["documents"][0][0] == "test document"
print("CRUD verification passed")
- Verify persistent storage:
import chromadb
# Write
client = chromadb.PersistentClient(path="./chroma_verify")
collection = client.get_or_create_collection("verify_persist")
collection.add(documents=["persisted doc"], ids=["id1"])
# Read in new client
client2 = chromadb.PersistentClient(path="./chroma_verify")
collection2 = client2.get_collection("verify_persist")
docs = collection2.get(ids=["id1"])
assert docs["documents"][0] == "persisted doc"
print("Persistence verification passed")
- Verify server mode:
# Terminal 1: start server
chroma run --path ./chroma_db --port 8000
# Terminal 2: verify connection
python -c "import chromadb; c = chromadb.HttpClient(host='localhost', port=8000); print(c.heartbeat())"
# Expected output: a nanosecond timestamp integer
- Verify metadata filtering:
import chromadb
client = chromadb.Client()
collection = client.create_collection("filter_verify")
collection.add(
documents=["doc1", "doc2", "doc3"],
metadatas=[{"cat": "a"}, {"cat": "b"}, {"cat": "a"}],
ids=["id1", "id2", "id3"]
)
results = collection.query(query_texts=["doc"], n_results=10, where={"cat": "a"})
assert len(results["ids"][0]) == 2
print("Filter verification passed")
Performance Reference
| Operation | Latency | Notes |
|---|---|---|
| Add 100 docs | ~1-3s | With default embedding (all-MiniLM-L6-v2) |
| Query (top 10) | ~50-200ms | Depends on collection size |
| Metadata filter | ~10-50ms | Fast with proper indexing |
Best Practices
- Use
PersistentClient— don't lose data on restart - Add metadata to every document — enables filtering and tracking
- Batch operations — add multiple docs at once (1,000-5,000 per batch)
- Choose the right embedding model — balance speed vs quality
- Use
wherefilters to narrow search space before vector search - Use unique, deterministic IDs — avoid collisions and silent overwrites
- Regular backups — copy the
chroma_dbdirectory - Monitor collection size — scale to server mode for multi-user production
- Test embedding functions on your domain data — ensure quality
- Use server mode for production — better for concurrent access
Resources
- GitHub: https://github.com/chroma-core/chroma
- Docs: https://docs.trychroma.com
- Discord: https://discord.gg/MMeYNTmh3x
- Version: 1.3.3+
- License: Apache 2.0