{"record":{"id":"685233eab5c94e3d","repo":"chroma-core/chroma","slug":"expected-metadata-value-to-be-a-str-int-float-b","errorCode":null,"errorMessage":"Expected metadata value to be a str, int, float, bool, SparseVector, list, or None, got {value} which is a {type(value).__name__}","messagePattern":"Expected metadata value to be a str, int, float, bool, SparseVector, list, or None, got (.+?) which is a (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"chromadb/api/types.py","lineNumber":1099,"sourceCode":"    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__)\n        if isinstance(value, SparseVector):\n            pass  # Already validated in SparseVector.__post_init__\n        elif isinstance(value, list):\n            _validate_metadata_list_value(key, value)\n        # isinstance(True, int) evaluates to True, so we need to check for bools separately\n        elif not isinstance(value, bool) and not isinstance(\n            value, (str, int, float, type(None))\n        ):\n            raise ValueError(\n                f\"Expected metadata value to be a str, int, float, bool, SparseVector, list, or None, got {value} which is a {type(value).__name__}\"\n            )\n    return metadata\n\n\ndef validate_update_metadata(metadata: UpdateMetadata) -> UpdateMetadata:\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)}\"\n        )\n    if metadata is None:\n        return metadata\n    if len(metadata) == 0:\n        raise ValueError(f\"Expected metadata to be a non-empty dict, got {metadata}\")\n    for key, value in metadata.items():\n        if not isinstance(key, str):\n            raise ValueError(f\"Expected metadata key to be a str, got {key}\")","sourceCodeStart":1081,"sourceCodeEnd":1117,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/api/types.py#L1081-L1117","documentation":"Scalar metadata values in the insert path may be str, int, float, bool, None, a SparseVector, or a homogeneous list of str/int/float/bool (list rules enforced by _validate_metadata_list_value). Anything else - nested dicts, tuples, sets, datetime/date, Decimal, bytes, numpy scalars - raises this ValueError naming the offending value and type. This is Chroma's flat-metadata constraint: where-filters only operate on scalar fields.","triggerScenarios":"metadatas=[{'created_at': datetime.now()}]; {'price': Decimal('1.99')}; {'nested': {'a': 1}}; {'pair': (1, 2)} (tuple is not a list); {'conf': np.float32(0.9)}; bytes blobs from binary columns.","commonSituations":"Datetime columns from ORMs or pandas (df datetime64 values via .item() are still datetime objects); Decimal from money columns; nested JSON from webhooks; numpy scalars leaking from vectorized code; tuple-valued fields assumed to count as lists.","solutions":["Flatten or serialize: timestamps to .isoformat() strings or epoch int/float; Decimal to float(); nested structures to json.dumps(...) stored as str","Convert numpy scalars with .item() before building metadata","Keep metadata strictly scalar and move rich content into documents (free text)","Write one normalize_metadata() helper and route every write path through it"],"exampleFix":"# before\nmeta = {'created_at': row['ts'], 'price': row['price']}  # datetime, Decimal\n\n# after\nmeta = {'created_at': row['ts'].isoformat(), 'price': float(row['price'])}","handlingStrategy":"type-guard","validationCode":"import json\nfrom datetime import datetime, date\n\ndef normalize_meta_value(v):\n    if isinstance(v, (datetime, date)):\n        return v.isoformat()\n    if hasattr(v, 'item'):  # numpy scalar\n        return v.item()\n    if isinstance(v, tuple):\n        return list(v)\n    if isinstance(v, (dict, set, bytes)):\n        return json.dumps(v, default=str) if not isinstance(v, bytes) else v.decode('utf-8', 'replace')\n    if v.__class__.__name__ == 'Decimal':\n        return float(v)\n    return v\n\ndef normalize_metadata(meta):\n    return {k: normalize_meta_value(v) for k, v in meta.items()}","typeGuard":"def is_flat_metadata_value(v) -> bool:\n    if v is None or isinstance(v, (str, int, float, bool)):\n        return True\n    if isinstance(v, list) and v:\n        ts = {bool if isinstance(x, bool) else type(x) for x in v}\n        return len(ts) == 1 and next(iter(ts)) in (str, int, float, bool)\n    return False","tryCatchPattern":"try:\n    collection.add(ids=ids, metadatas=metas)\nexcept ValueError as e:\n    if 'Expected metadata value to be a str, int, float, bool' in str(e):\n        metas = [normalize_metadata(m) for m in metas]\n        collection.add(ids=ids, metadatas=metas)\n    else:\n        raise","preventionTips":["Route every write through one normalize_metadata() helper","Store timestamps as isoformat strings or epoch numbers","Call .item() on numpy scalars; float() on Decimals","Keep metadata flat - nested structures go into documents or serialized strings"],"tags":["chromadb","python","metadata","type-safety","validation"],"backgroundTag":"unsupported-metadata-value-type","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}