This episode covers metadata filtering with the where clause: the $eq, $ne, $in, $nin, $gt, $lt, $gte, and $lte operators, nested metadata, $and and $or logic, and document search via $contains, $not_contains, and regex.

Semantic search alone is not always enough. Imagine you have millions of news articles and want to answer "how much did inflation rise last month?" — without knowing when "last month" was. This is where metadata filtering comes in: narrowing data by structured attributes before or during the vector search.
Episode 7 covers the where clause thoroughly: comparison operators, nested metadata, $and and $or combinations, and where_document for searching inside document text using $contains and regex. These filters are what turn ChromaDB from merely "find similar" into "find similar with conditions".
The where clause is a dict: the key is the metadata field name, the value is an operator dict. ChromaDB provides eight basic operators:
$eq: equals$ne: not equals$in: in the list$nin: not in the list$gt: greater than$lt: less than$gte: greater than or equal to$lte: less than or equal tohasil = collection.query(
query_texts=["inflasi"],
n_results=10,
where={"kategori": "ekonomi"},
)The example above applies shorthand: where={"kategori": "ekonomi"} is the same as $eq. Only items with the metadata kategori: ekonomi are considered in the vector search.
Multiple conditions on the same field are written in sequence in one dict:
hasil = collection.get(
where={"tanggal": {"$gte": "2026-06-01", "$lt": "2026-07-01"}},
)where={"tanggal": {"$gte": "2026-06-01", "$lt": "2026-07-01"}} retrieves all items with a date in June 2026. This is an important pattern for temporal filtering — for example, the retention policy we will discuss in episode 16.
$in and $nin are very useful for category or status filters:
hasil = collection.get(
where={"status": {"$in": ["published", "draft"]}},
)
hasil = collection.get(
where={"status": {"$nin": ["archived", "deleted"]}},
)where={"status": {"$in": ["published", "draft"]}} retrieves items whose status is in the list; $nin does the opposite. These combinations replace many verbose $or clauses.
ChromaDB metadata can be nested — keys like "penulis.nama" are automatically interpreted as access into a nested dict:
hasil = collection.get(
where={"penulis.nama": {"$eq": "Arman"}},
)If an item's metadata is {"penulis": {"nama": "Arman", "role": "admin"}}, then where={"penulis.nama": {"$eq": "Arman"}} matches it. Nested access uses a dot as the level separator. Nested metadata makes it easier to build a clean data model — for example, putting full context like author, version, and source under a single key, which will be covered fully in episode 10.
For cross-field conditions, use $and or $or. Both accept a list of where clauses:
hasil = collection.get(
where={
"$and": [
{"kategori": "ekonomi"},
{"tanggal": {"$gte": "2026-06-01"}},
]
},
)hasil = collection.get(
where={
"$or": [
{"kategori": "ekonomi"},
{"kategori": "keuangan"},
]
},
)where={"$and": [...]} requires all conditions to be met; where={"$or": [...]} only needs one. Both can be nested — for example where={"$and": [{"$or": [...]}, {"status": "published"}], ...} — so ChromaDB fully evaluates a complex condition tree.
Besides metadata, ChromaDB can filter by document contents via where_document. The main operators are $contains (contains text) and $not_contains:
hasil = collection.query(
query_texts=["database"],
n_results=5,
where_document={"$contains": "ChromaDB"},
)where_document={"$contains": "ChromaDB"} only returns documents containing the word "ChromaDB". This is a full-text filter that works within a single vector query — a bridge toward episode 8.
For more flexible patterns, ChromaDB supports regex in where_document:
hasil = collection.get(
where_document={"$contains": "v[0-9]+\.[0-9]+"},
)where_document={"$contains": "v[0-9]+\.[0-9]+"} matches documents containing patterns like v1.5 or v2.3 — useful for finding software versions. Remember the escaping rule: regex is written in a Python string, so backslashes need to be doubled if necessary.
In production, filters are used all at once — metadata and documents:
hasil = collection.query(
query_texts=["rekomendasi database vektor"],
n_results=3,
where={
"$and": [
{"status": "published"},
{"topik": "database"},
]
},
where_document={"$contains": "Chroma"},
)The pattern above — semantic search constrained by status, topic, and document contents — is a realistic RAG template. collection.query(...) evaluates all filters before ranking results by vectors.
Episode 7 equipped you with precision: the where clause for filtering metadata with eight operators, nested metadata access, $and and $or logic, and where_document for text and regex search inside documents. Filters are the difference between results that are "similar" and results that are "exact".
Key takeaways:
where filters metadata; where_document filters document contents.$eq, $ne, $in, $nin, $gt, $lt, $gte, $lte.penulis.nama.$and and $or can be nested for complex logic.$contains and $not_contains for full-text in documents.where_document opens up pattern search like software versions.In the next episode, episode 8, we will discuss full-text and hybrid search — fuller keyword search with relevance scoring, strategies for combining vector, full-text, and metadata, and fusion and rerank approaches for accurate RAG results. This brings ChromaDB one step closer to production.