{"record":{"id":"ee1e09e9eb5b5836","repo":"chroma-core/chroma","slug":"expected-metadatas-to-be-a-list-got-metadatas","errorCode":null,"errorMessage":"Expected metadatas to be a list, got {metadatas}","messagePattern":"Expected metadatas to be a list, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"chromadb/api/types.py","lineNumber":1180,"sourceCode":"    Returns:\n        Metadata dictionary with serialized SparseVectors converted to dataclass instances\n    \"\"\"\n    if metadata is None:\n        return None\n\n    result: Dict[str, Any] = {}\n    for key, value in metadata.items():\n        if isinstance(value, dict) and value.get(TYPE_KEY) == SPARSE_VECTOR_TYPE_VALUE:\n            result[key] = SparseVector.from_dict(value)\n        else:\n            result[key] = value\n    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","sourceCodeStart":1162,"sourceCodeEnd":1198,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/api/types.py#L1162-L1198","documentation":"ChromaDB requires the `metadatas` argument to be a Python list (or JSON array) where every element is a dict of string keys to str/int/float/bool values. `validate_metadatas` in chromadb/api/types.py runs as part of request validation on add/upsert (and on query result decoding), and throws this ValueError the moment the top-level object is not a list. It is a client-side guard, so it fires before any data reaches the server.","triggerScenarios":"Calling collection.add(...) or collection.upsert(...) with metadatas={\"source\": \"wiki\"} (a single dict instead of a list of dicts), metadatas=None/JSON string, or a numpy array / pandas Series instead of a plain list. Also triggered when a JSON REST payload sends an object rather than an array for metadatas.","commonSituations":"Wrapping/unwrapping bugs when migrating from other vector DBs (Pinecone/OpenSearch take dicts), passing metadata for a single record without enclosing it in [ ], deserializing metadatas from JSON files that stored one object, or accidentally passing the embeddings/ids argument positionally into the metadatas slot.","solutions":["Wrap single metadata in a list: metadatas=[{\"source\": \"wiki\"}] and keep one metadata per id/document/embedding.","Check argument order if calling positionally: add(ids, embeddings, metadatas, documents) — a dict landing in metadatas is usually a shifted positional argument.","If loading from JSON/pandas, coerce with list(df[\"meta\"].map(dict)) or json.load then ensure the value is a list before passing.","Never JSON-encode metadatas yourself; pass native Python objects."],"exampleFix":"# before\ncollection.add(ids=[\"1\"], documents=[\"hello\"], metadatas={\"src\": \"wiki\"})\n# after\ncollection.add(ids=[\"1\"], documents=[\"hello\"], metadatas=[{\"src\": \"wiki\"}])","handlingStrategy":"type-guard","validationCode":"from typing import Any\n\ndef is_valid_metadatas(metadatas: Any) -> bool:\n    if not isinstance(metadatas, list) or not metadatas:\n        return False\n    return all(\n        isinstance(m, dict)\n        and all(isinstance(k, str) and isinstance(v, (str, int, float, bool)) for k, v in m.items())\n        for m in metadatas\n    )\n\nassert is_valid_metadatas(metadatas), \"metadatas must be a list of flat str->scalar dicts\"","typeGuard":"def is_metadatas(value: Any) -> TypeGuard[list[dict[str, str | int | float | bool]]]:\n    return (\n        isinstance(value, list)\n        and all(isinstance(m, dict) for m in value)\n        and all(\n            isinstance(k, str) and isinstance(v, (str, int, float, bool))\n            for m in value for k, v in m.items()\n        )\n    )","tryCatchPattern":"try:\n    collection.add(ids=ids, documents=docs, metadatas=metadatas)\nexcept ValueError as e:\n    if \"Expected metadatas to be a list\" in str(e):\n        metadatas = [metadatas] if isinstance(metadatas, dict) else metadatas\n        raise  # or retry once with corrected shape\n    raise","preventionTips":["Always construct metadatas as a list comprehension parallel to ids: metadatas=[build_meta(i) for i in items].","Assert len(metadatas) == len(ids) before every add/upsert — mismatched shapes usually accompany wrong types.","Never pass a single dict; wrap in [ ] even for one record.","Run chromadb.api.types.validate_metadatas directly in test suites for filter/ingestion builders."],"tags":["chromadb","validation","metadata","ingestion"],"backgroundTag":"metadata-validation-failed","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}