{"record":{"id":"c9d823cb145d6c67","repo":"chroma-core/chroma","slug":"expected-metadata-to-be-a-dict-or-none-got-type","errorCode":null,"errorMessage":"Expected metadata to be a dict or None, got {type(metadata).__name__} as metadata","messagePattern":"Expected metadata to be a dict or None, got (.+?) as metadata","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"chromadb/api/types.py","lineNumber":1072,"sourceCode":"            f\"Expected metadata list value for key '{key}' to be non-empty\"\n        )\n    first_type = type(value[0])\n    # Normalize: bool must be checked before int since isinstance(True, int) is True\n    if isinstance(value[0], bool):\n        first_type = bool\n    for item in value:\n        item_type = bool if isinstance(item, bool) else type(item)\n        if item_type is not first_type or item_type not in (str, int, float, bool):\n            raise ValueError(\n                f\"Expected metadata list value for key '{key}' to contain only str, int, float, or bool \"\n                f\"and all elements must be the same type, got {value}\"\n            )\n\n\ndef validate_metadata(metadata: Metadata) -> Metadata:\n    \"\"\"Validates metadata to ensure it is a dictionary of strings to strings, ints, floats, bools, SparseVectors, or lists thereof\"\"\"\n    if not isinstance(metadata, dict) and metadata is not None:\n        raise ValueError(\n            f\"Expected metadata to be a dict or None, got {type(metadata).__name__} as metadata\"\n        )\n    if metadata is None:\n        return metadata\n    if len(metadata) == 0:\n        raise ValueError(\n            f\"Expected metadata to be a non-empty dict, got {len(metadata)} metadata attributes\"\n        )\n    for key, value in metadata.items():\n        if key == META_KEY_CHROMA_DOCUMENT:\n            raise ValueError(\n                f\"Expected metadata to not contain the reserved key {META_KEY_CHROMA_DOCUMENT}\"\n            )\n        if not isinstance(key, str):\n            raise TypeError(\n                f\"Expected metadata key to be a str, got {key} which is a {type(key).__name__}\"\n            )\n        # Check if value is a SparseVector (validation happens in __post_init__)","sourceCodeStart":1054,"sourceCodeEnd":1090,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/api/types.py#L1054-L1090","documentation":"Each element of the metadatas= list passed to add/upsert (and the metadata dict passed to collection.modify) must be a dict or None. validate_metadata (chromadb/api/types.py:1069, reached per record via validate_metadatas) raises when it receives anything else, most commonly a JSON string that was never parsed, a list-of-lists, or a scalar. The message includes the actual type name (e.g. 'got str as metadata').","triggerScenarios":"metadatas=[json.dumps(row) for row in rows] (strings instead of dicts); metadatas=[('a', 1)] (tuples from itertuples); metadatas='not-json' (a bare string that gets wrapped as a single non-dict element); collection.modify(metadata=[1,2]).","commonSituations":"Forgetting json.loads on payloads from queues or HTTP; passing dataframe itertuples output directly; double-wrapping ([[{...}]]) from list comprehensions; caching layers that return serialized metadata.","solutions":["Parse before sending: metadatas=[json.loads(m) if isinstance(m, str) else m for m in metadatas]","Ensure the shape is List[Optional[dict]] - one dict (or None) per record","Use None for records without metadata rather than empty or placeholder values","Type-check at the ingestion boundary before calling Chroma"],"exampleFix":"# before\nmetadatas=[cache.get(i) for i in ids]  # cached JSON strings\n\n# after\nmetadatas=[json.loads(cache.get(i)) if isinstance(cache.get(i), str) else cache.get(i) for i in ids]","handlingStrategy":"type-guard","validationCode":"import json\n\ndef ensure_metadata_dicts(metadatas):\n    return [json.loads(m) if isinstance(m, str) else m for m in metadatas]\n\nmetadatas = ensure_metadata_dicts(metadatas)","typeGuard":"def is_valid_metadata_element(m) -> bool:\n    return m is None or isinstance(m, dict)","tryCatchPattern":"try:\n    collection.add(ids=ids, metadatas=metas)\nexcept ValueError as e:\n    if 'Expected metadata to be a dict or None' in str(e):\n        metas = [json.loads(m) if isinstance(m, str) else m for m in metas]\n        collection.add(ids=ids, metadatas=metas)\n    else:\n        raise","preventionTips":["json.loads any serialized payload before passing it as metadata","Keep the shape List[Optional[dict]] - one dict per record","Use None for records without metadata"],"tags":["chromadb","python","metadata","type-check","validation"],"backgroundTag":"invalid-metadata-type","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}