1. Your embeddings move as they are
Export the vectors Chroma already stores and index them directly. Keep the same embedding model for queries: no re-embedding, no new model bill, no change to your ingestion pipeline's model.
Chroma Cloud migration Chroma Cloud stops accepting writes on November 22, 2026 and stops serving reads on December 22. Bonsai runs your vectors, documents and metadata on managed OpenSearch for a flat monthly price, and you can start testing on a free cluster today.
Form not loading? Contact our engineers directly.
Read-only Applications can no longer add, update, upsert or delete data in Chroma Cloud. Your index stops changing from this day. Reads stop Chroma Cloud stops serving queries. Everything you want to keep has to be exported before this date. Source: Chroma's wind-down notice, published October 23, 2026. Self-hosted, open-source ChromaDB is not affected. Export the vectors Chroma already stores and index them directly. Keep the same embedding model for queries: no re-embedding, no new model bill, no change to your ingestion pipeline's model. Chroma Cloud charges for the size of the collection each query searches, and every filter multiplies it. On Bonsai you pay for the plan. Adding a filter, raising A Sandbox cluster costs nothing, so you can load a sample and check results before the November 22 deadline, then change plans in the dashboard when you're ready. OpenSearch ranks text with BM25 and vectors with k-NN in the same index, and combines them in one hybrid query. Full-text filters are ordinary queries, not a metered extra. See hybrid search pipelines. Send us your collection sizes and query patterns and a search engineer will check the plan, the mapping and your migration script. See how Bonsai works. OpenSearch is Apache 2.0 software with official clients for Python, JavaScript, Java, Go, Ruby and more. Your mappings and queries work on any OpenSearch cluster, so you are not tied to one vendor's API. See supported versions.Move off Chroma Cloud before it goes read-only on November 22
Talk to a search engineer
The Chroma Cloud wind-down
Why Chroma Cloud teams move to Bonsai
1. Your embeddings move as they are
2. Query volume doesn't change the bill
k or doubling traffic doesn't add a line item. See pricing.3. Free to try, today
4. Vector, keyword and hybrid search together
5. A search engineer checks your plan
6. An open-source engine
Chroma Cloud prices a query by the size of the collection it searches, multiplied by query units. A plain vector search is one unit. Each metadata predicate adds one, and a full-text or regex filter with an N-character string adds N - 2. A 2-million-record RAG collection with 1536-dimension embeddings, queried 5 million times a month with one metadata filter, comes to about $1,150 a month, and almost all of that is query cost. Swap the metadata filter for an 11-character full-text filter and it's about $5,700.
Workloads split across many small collections, like the defaults in Chroma's own calculator, see much lower query costs, because each query searches less data. If that's you, the calculator below will say so.
Chroma Cloud rates from docs.trychroma.com/cloud/pricing, as of November 2, 2026. Bonsai plan limits and prices are on bonsai.io/pricing. Enter your workload, or start from an example. The Chroma Cloud side applies Chroma's published rates. The Bonsai side estimates disk and memory for an OpenSearch index using disk-based vector search, then picks the cheapest plan that fits. Sizes use the numbers you'd see in Chroma's dashboard: record count, embedding dimension, and average document and metadata bytes. Start from an example Disk k-NN graph memory No charge per query, per write or per GiB returned. Chroma Cloud (Chroma bills in binary units: 1 GiB = 230 bytes, 1 TiB = 240 bytes) Records are assumed to be spread evenly across collections, and each query searches one collection. Bonsai (plan limits are in decimal GB) The 1.0 and 1.5 disk factors and the 50% memory budget are assumptions until they're measured on your data. Sandbox and Staging plans are left out because they're not meant for production. We size every migration with you before you commit. Estimates only, not a quote. Chroma Cloud rates as of November 2, 2026; Bonsai plans as of November 2, 2026, see bonsai.io/pricing. Sign up, create a Sandbox cluster and load a sample collection. You'll see your own queries running on Bonsai before you pick a paid plan. Sign up and create a Sandbox cluster. It's free and holds up to 35,000 documents, enough to test your mapping and queries on a sample. Copy the migration skill below into Claude Code, Cursor or another coding agent. It inventories your collections and writes the export, load and validation scripts. The agent loads a few thousand records into the Sandbox cluster and checks counts and top-10 overlap against Chroma, so you see real results before you pay for anything. Change the plan to the one the calculator picked, run the full load, and switch your application's reads to Bonsai. This skill gives a coding agent the whole migration plan: what to inspect, the index mapping, the export and load script, how to validate, and how to rewrite each Chroma call in your code. It reads from Chroma and never deletes anything there. Claude Code: save it as No. Chroma Cloud's $0.33 per GiB-month storage rate is cheaper per gigabyte than any Standard plan, so small, low-traffic or archival collections, or data split across hundreds of tiny collections, can cost less there. Bonsai comes out ahead when queries run against large collections, use filters, or return many results. Try the Small, low traffic example in the calculator to see both sides. Yes. A Sandbox cluster is free and holds up to 35,000 documents, enough to test the mapping, a sample load and your queries. When you're ready, change the cluster's plan in the dashboard. Sign up to create one. No. The migration skill exports the embeddings Chroma already stores and indexes them into a OpenSearch has BM25 full-text search built in, and it can combine BM25 and k-NN scores in one query with a search pipeline. Neither is metered per query on Bonsai. See hybrid search pipelines. Put collections that share an embedding model into one index and store the collection name in a Yes. Open-source ChromaDB continues under the Apache 2.0 license; only Chroma Cloud is shutting down. You would then run, scale, back up and patch the database yourself. The Chroma Cloud side follows Chroma's published billing rules. The Bonsai side is a sizing model with assumptions listed under the calculator; real disk use depends on your documents and metadata. Send us your numbers and we'll check the plan before you migrate. Start on a free Sandbox cluster now, or send us your collection sizes and query volume and a search engineer will check the plan with you.How Chroma Cloud bills vs. how Bonsai bills
Criterion Bonsai Chroma Cloud What you pay for A monthly plan, sized by disk, memory and document count Metered usage (writes, storage, data queried, data returned), plus a plan fee on Team Writes No charge for write volume. As fast as you can write it, we can take it! $2.50 per logical GiB added, updated or upserted Storage Included up to the plan's data size $0.33 per GiB-month of logical (uncompressed) data Queries Included. Standard plans cap concurrent searches, not query volume $0.0075 per TiB of collection searched, per query unit. Each metadata filter adds a unit; a full-text filter of N characters adds N - 2 Results returned Included $0.09 per GiB Engine OpenSearch, open source under Apache 2.0, with clients for every major language Chroma Estimate your monthly cost
Chroma Cloud, per month
Bonsai, per month
Assumptions and formulas
logical bytes = records × (dimension × 4 + document bytes + metadata bytes)
storage = logical GiB × $0.33
writes = monthly writes × bytes per record ÷ GiB × $2.50
query units = 1 + metadata filters + (full-text characters - 2)
query = monthly queries × query units × (logical bytes ÷ collections) ÷ TiB × $0.0075
data returned = monthly queries × k × (document + metadata bytes) ÷ GiB × $0.09
total = plan fee + max(usage - plan credit, 0)
graph bytes = 1.1 × (dimension ÷ 8 + 8 × 16) HNSW over 1-bit vectors, m = 16
disk bytes = records × (dimension × 4 full-precision vectors kept for rescoring
+ graph bytes
+ document bytes × 1.0 stored text and inverted index
+ metadata bytes × 1.5)
memory bytes = records × graph bytes
plan = cheapest Standard or Business plan where disk ≤ data size,
memory ≤ 50% of plan memory, and records ≤ the plan's document limit
Test it on a free Sandbox cluster
How the migration works
Create a free Sandbox cluster
Give your agent the skill
Load a sample and compare
Upgrade and cut over
Let your coding agent do the migration
.claude/skills/chroma-cloud-to-bonsai/SKILL.md in your repo and ask Claude to migrate off Chroma Cloud. Other agents: paste it into the chat, or add it to your rules or instructions file.---
name: chroma-cloud-to-bonsai
description: Migrate an application from Chroma Cloud to Bonsai managed OpenSearch. Inventories Chroma collections, creates a k-NN index with disk-based vectors, exports and bulk-loads records with their existing embeddings, validates counts and result overlap, and rewrites Chroma calls in the codebase to OpenSearch. Use when asked to move off Chroma Cloud, migrate Chroma to OpenSearch or Bonsai, or replace chromadb in an app.
---
# Migrate from Chroma Cloud to Bonsai (OpenSearch)
Chroma Cloud goes read-only on November 22, 2026 (no add, update, upsert or delete) and stops serving reads on December 22, 2026. Work in the order below, report after each step, and ask before anything that writes to a production cluster.
## Rules
- Read credentials from environment variables. Never print, log or commit them.
- Never delete or modify data in Chroma. This migration only reads from it.
- Keep the embedding model. Existing vectors are exported as they are; query vectors must come from the same model.
- Test against a Bonsai Sandbox cluster with a sample first, then run the full load on the production plan.
- If a step fails twice, stop and report what happened instead of improvising.
## Inputs
Ask the user for anything missing:
- `CHROMA_API_KEY`, `CHROMA_TENANT`, `CHROMA_DATABASE` for Chroma Cloud.
- `BONSAI_URL`: the full access URL from the Bonsai dashboard (`https://user:pass@host`). New accounts can create a free Sandbox cluster at https://app.bonsai.io/signup?utm_source=bonsai-labs&utm_campaign=%2Flabs%2Fchroma-cloud-migration%2F&utm_content=agent-skill
- Python 3.9+ with `pip install chromadb opensearch-py`.
## 1. Inventory Chroma
Write and run a read-only script that connects with `chromadb.CloudClient(tenant=..., database=..., api_key=...)` and, for every collection, reports:
- name and `collection.count()`
- embedding dimension (length of the first embedding from `collection.get(limit=1, include=["embeddings"])`)
- distance metric, from `collection.configuration` (`hnsw.space`) or the legacy `collection.metadata["hnsw:space"]`; Chroma's default is `l2`
- metadata keys and value types, from a sample of about 1,000 records
- average document and metadata size in bytes, from the same sample
Print a table, then the totals. Tell the user they can check the plan size at https://bonsai.io/labs/chroma-cloud-migration/#calculator. Sandbox clusters hold about 125 MB and 35,000 documents, enough for a sample of a few thousand records at 1536 dimensions.
## 2. Find Chroma in the codebase
Search for `chromadb`, `CloudClient`, `HttpClient`, `get_or_create_collection`, `.add(`, `.upsert(`, `.update(`, `.delete(`, `.query(`, `.get(`, `where=` and `where_document=`. List every call site with its file, line and the operation. Note any LangChain or LlamaIndex Chroma vector store wrappers, which have OpenSearch equivalents (`OpenSearchVectorSearch`, `OpensearchVectorStore`).
## 3. Create the index
Create one index per embedding dimension and keep the Chroma collection name in a `collection` keyword field. Hundreds of small indexes would each use shards, and Bonsai plans limit shard count. On a Sandbox cluster, set `"number_of_shards": 1`.
Map the distance metric to `space_type`: `l2` to `l2`, `cosine` to `cosinesimil`, `ip` to `innerproduct`. If the cluster rejects the space type, show the error to the user before trying anything else.
```json
PUT chroma-1536
{
"settings": {
"index": { "knn": true, "knn.derived_source.enabled": true, "number_of_shards": 1 }
},
"mappings": {
"dynamic_templates": [
{
"metadata_strings": {
"path_match": "metadata.*",
"match_mapping_type": "string",
"mapping": { "type": "keyword" }
}
}
],
"properties": {
"embedding": {
"type": "knn_vector",
"dimension": 1536,
"mode": "on_disk",
"compression_level": "32x",
"method": {
"name": "hnsw",
"engine": "faiss",
"space_type": "innerproduct",
"parameters": { "ef_construction": 256, "m": 16 }
}
},
"document": { "type": "text" },
"metadata": { "type": "object" },
"collection": { "type": "keyword" }
}
}
}
```
`mode: on_disk` with `compression_level: 32x` builds the HNSW graph over 1-bit quantized vectors in memory and rescores top candidates with the full-precision vectors on disk. It needs OpenSearch 2.19 or later; check with `GET /`. If a metadata key holds different types across records, map it explicitly before loading.
## 4. Export and bulk load
Write a script that pages through each collection and streams records into the index with `opensearchpy.helpers.bulk`:
- `collection.get(include=["embeddings", "documents", "metadatas"], limit=500, offset=offset)`, until a page returns no ids
- document `_id` = the Chroma id; `_source` = `embedding`, `document`, `metadata` (use `{}` for `None`), `collection`
- write each vector component as `float(f"{x:.9g}")`. Chroma returns float32, and 9 significant digits round-trip a float32 exactly, so bulk requests stay much smaller than Python's default 17 digits
- a `--limit` flag for sample runs, and a checkpoint file with the last offset per collection so a failed run can resume
- `raise_on_error=False`; print the first few errors and the totals at the end
Run it with `--limit 2000` against the Sandbox cluster first, then run steps 5 and 6 against that sample before the full load.
## 5. Validate
- Compare `collection.count()` with `GET <index>/_count` filtered by `collection` for every collection.
- Take 200 recent or representative queries. If the app logs no queries, use the documents of 200 random records as queries. Run each against Chroma (`n_results=10`) and OpenSearch (`k=10`) with the same filters, and report the average overlap of the top 10 ids.
- If the overlap is low, check the space type first. Report the numbers to the user instead of changing compression or engine settings on your own.
## 6. Rewrite the application code
Replace each call site from step 2. Keep a thin wrapper module so the rest of the code does not import the OpenSearch client directly.
| Chroma | OpenSearch |
| ----------------------------------------------------- | -------------------------------------------------------- |
| `collection.add` / `upsert` | `_bulk` `index` actions with `_id` = Chroma id |
| `collection.update` | `_bulk` `update` actions with a partial `doc` |
| `collection.delete(ids=...)` | `_bulk` `delete` actions |
| `collection.delete(where=...)` | `_delete_by_query` with the translated filter |
| `collection.get(ids=...)` | `_mget` |
| `collection.query(query_embeddings=[v], n_results=k)` | `knn` query on `embedding` with `"k": k` and `"size": k` |
| `collection.count()` | `_count` with a `term` on `collection` |
Every query and count must include `{ "term": { "collection": "<name>" } }` in its filter.
Filter translation, applied inside the `knn` query's `filter` (a `bool` query):
| Chroma `where` / `where_document` | OpenSearch |
| --------------------------------- | ------------------------------------------------------------------------ |
| `{"k": v}` or `{"k": {"$eq": v}}` | `term` on `metadata.k` |
| `$ne` | `bool.must_not` with `term` |
| `$gt`, `$gte`, `$lt`, `$lte` | `range` |
| `$in`, `$nin` | `terms`, or `must_not` with `terms` |
| `$and`, `$or` | `bool.filter`, `bool.should` with `minimum_should_match: 1` |
| `$contains`, `$not_contains` | `match_phrase` on `document`, or `must_not` with it |
| `$regex`, `$not_regex` | `regexp` on `document`; warn the user that this is slow on large indexes |
Chroma returns distances (lower is closer) and OpenSearch returns scores (higher is closer). Find any code that sorts or thresholds on distance and adjust it. Ask before changing a threshold value.
## 7. Cut over
1. Before November 22, write to both Chroma and Bonsai, or pause writes, so the two stay in step.
2. Run the full load on the production plan, then step 5 again on the full data.
3. Switch reads to Bonsai behind a config flag, and keep the Chroma read path until December 22 in case you need to compare.
4. Summarize for the user what changed, the validation numbers, and anything left to do.
Help from a Bonsai search engineer: https://bonsai.io/labs/chroma-cloud-migration/#contact
Frequently asked questions
Is Bonsai cheaper for every workload?
Can I try Bonsai before I pay?
Do I have to re-embed my documents?
knn_vector field with the same dimension. Keep generating query vectors with the same model you use today.What about hybrid and full-text search?
Which OpenSearch version do I need for disk-based vectors?
mode: on_disk needs OpenSearch 2.17 or later. Standard plans offer OpenSearch 3 in us-east-1, us-west-2 and eu-west-1; see version support. Tell us your plan and region on the form and we'll confirm the index settings before you load data.How do Chroma collections map to OpenSearch?
keyword field, then filter on it. Hundreds of small indexes would each use shards, and Standard plans cap shard count. Collections with different dimensions need separate indexes.Can I stay on Chroma by self-hosting it?
How accurate is the estimate?
Get your data out before November 22