{"record":{"id":"7ef98c2e58f86118","repo":"chroma-core/chroma","slug":"expected-where-to-be-a-dict-got-where","errorCode":null,"errorMessage":"Expected where to be a dict, got {where}","messagePattern":"Expected where to be a dict, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"chromadb/api/types.py","lineNumber":1192,"sourceCode":"    return result\n\n\ndef validate_metadatas(metadatas: Metadatas) -> Metadatas:\n    \"\"\"Validates metadatas to ensure it is a list of dictionaries of strings to strings, ints, floats or bools\"\"\"\n    if not isinstance(metadatas, list):\n        raise ValueError(f\"Expected metadatas to be a list, got {metadatas}\")\n    for metadata in metadatas:\n        validate_metadata(metadata)\n    return metadatas\n\n\ndef validate_where(where: Where) -> None:\n    \"\"\"\n    Validates where to ensure it is a dictionary of strings to strings, ints, floats or operator expressions,\n    or in the case of $and and $or, a list of where expressions\n    \"\"\"\n    if not isinstance(where, dict):\n        raise ValueError(f\"Expected where to be a dict, got {where}\")\n    if len(where) != 1:\n        raise ValueError(f\"Expected where to have exactly one operator, got {where}\")\n    for key, value in where.items():\n        if not isinstance(key, str):\n            raise ValueError(f\"Expected where key to be a str, got {key}\")\n        # $contains and $not_contains are only valid as operators within a\n        # field expression (e.g. {\"field\": {\"$contains\": val}}), not as\n        # top-level where keys.\n        if key in (\"$contains\", \"$not_contains\"):\n            raise ValueError(\n                f\"Expected where key to be a metadata field name or a logical \"\n                f\"operator ($and, $or), got {key}\"\n            )\n        if (\n            key != \"$and\"\n            and key != \"$or\"\n            and key != \"$in\"\n            and key != \"$nin\"","sourceCodeStart":1174,"sourceCodeEnd":1210,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/api/types.py#L1174-L1210","documentation":"ChromaDB's `where` filter must be a dict with exactly one top-level entry (a field name, `$and`, `$or`, `$in`, or `$nin`). `validate_where` rejects anything that is not a dict — lists of conditions, strings, or None — with this ValueError. The check runs client-side on every query/get/count/delete that passes a `where`, and is re-run server-side by the Rust/Segment API.","triggerScenarios":"Passing where=[{\"a\": 1}, {\"b\": 2}] (list of filters), where=\"a = 1\" (SQL-ish string), where=None explicitly instead of omitting the kwarg, or where built from an empty/uninitialized variable. Common with collection.query(where=...) and collection.get(where=...).","commonSituations":"Developers coming from SQL or Mongo who build filter strings or condition arrays; passing an optional filter that defaulted to a non-dict sentinel; double-encoding `where` as a JSON string before a REST call; or confusing the one-key rule with multi-key dicts (which raise a different error).","solutions":["Use dict syntax with exactly one top-level key: where={\"source\": \"wiki\"}.","Combine multiple conditions under $and/$or: where={\"$and\": [{\"a\": 1}, {\"b\": 2}]} instead of a list or a multi-key dict.","Omit the where kwarg entirely (or pass None) when no filter is needed rather than an empty string/list.","If building filters dynamically, assert isinstance(where, dict) before calling the API."],"exampleFix":"# before\ncollection.query(query_embeddings=[q], where=[{\"source\": \"wiki\"}, {\"year\": 2024}])\n# after\ncollection.query(query_embeddings=[q], where={\"$and\": [{\"source\": \"wiki\"}, {\"year\": 2024}]})","handlingStrategy":"type-guard","validationCode":"def is_valid_where(where):\n    return where is None or isinstance(where, dict)","typeGuard":"from typing import TypeGuard\nfrom chromadb.api.types import Where\n\ndef is_where(value: object) -> TypeGuard[Where]:\n    return isinstance(value, dict) and len(value) == 1 and all(isinstance(k, str) for k in value)","tryCatchPattern":"try:\n    collection.query(query_embeddings=[q], where=where)\nexcept ValueError as e:\n    if \"Expected where to be a dict\" in str(e):\n        raise ValueError(f\"Invalid where filter {where!r}: must be a single-key dict\") from e\n    raise","preventionTips":["Treat filters as data built by one helper function, never assembled ad hoc at call sites.","Omit the where kwarg when unfiltered instead of passing empty strings/lists.","Before calling the API, assert isinstance(where, dict) and len(where) == 1.","Call chromadb.api.types.validate_where in unit tests for every filter your app can generate."],"tags":["chromadb","validation","where-filter","query"],"backgroundTag":"invalid-query-filter","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}