chroma-core/chroma · error · ValueError
Expected metadata value to be a str, int, float, bool, Spars
Error message
Expected metadata value to be a str, int, float, bool, SparseVector, list, or None, got {value} which is a {type(value).__name__} What it means
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.
Source
Thrown at chromadb/api/types.py:1099
for key, value in metadata.items():
if key == META_KEY_CHROMA_DOCUMENT:
raise ValueError(
f"Expected metadata to not contain the reserved key {META_KEY_CHROMA_DOCUMENT}"
)
if not isinstance(key, str):
raise TypeError(
f"Expected metadata key to be a str, got {key} which is a {type(key).__name__}"
)
# Check if value is a SparseVector (validation happens in __post_init__)
if isinstance(value, SparseVector):
pass # Already validated in SparseVector.__post_init__
elif isinstance(value, list):
_validate_metadata_list_value(key, value)
# isinstance(True, int) evaluates to True, so we need to check for bools separately
elif not isinstance(value, bool) and not isinstance(
value, (str, int, float, type(None))
):
raise ValueError(
f"Expected metadata value to be a str, int, float, bool, SparseVector, list, or None, got {value} which is a {type(value).__name__}"
)
return metadata
def validate_update_metadata(metadata: UpdateMetadata) -> UpdateMetadata:
"""Validates metadata to ensure it is a dictionary of strings to strings, ints, floats, bools, SparseVectors, or lists thereof"""
if not isinstance(metadata, dict) and metadata is not None:
raise ValueError(
f"Expected metadata to be a dict or None, got {type(metadata)}"
)
if metadata is None:
return metadata
if len(metadata) == 0:
raise ValueError(f"Expected metadata to be a non-empty dict, got {metadata}")
for key, value in metadata.items():
if not isinstance(key, str):
raise ValueError(f"Expected metadata key to be a str, got {key}")View on GitHub (pinned to aecdd12c8a)
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
Example fix
# before
meta = {'created_at': row['ts'], 'price': row['price']} # datetime, Decimal
# after
meta = {'created_at': row['ts'].isoformat(), 'price': float(row['price'])} Defensive patterns
Strategy: type-guard
Validate before calling
import json
from datetime import datetime, date
def normalize_meta_value(v):
if isinstance(v, (datetime, date)):
return v.isoformat()
if hasattr(v, 'item'): # numpy scalar
return v.item()
if isinstance(v, tuple):
return list(v)
if isinstance(v, (dict, set, bytes)):
return json.dumps(v, default=str) if not isinstance(v, bytes) else v.decode('utf-8', 'replace')
if v.__class__.__name__ == 'Decimal':
return float(v)
return v
def normalize_metadata(meta):
return {k: normalize_meta_value(v) for k, v in meta.items()} Type guard
def is_flat_metadata_value(v) -> bool:
if v is None or isinstance(v, (str, int, float, bool)):
return True
if isinstance(v, list) and v:
ts = {bool if isinstance(x, bool) else type(x) for x in v}
return len(ts) == 1 and next(iter(ts)) in (str, int, float, bool)
return False Try / catch
try:
collection.add(ids=ids, metadatas=metas)
except ValueError as e:
if 'Expected metadata value to be a str, int, float, bool' in str(e):
metas = [normalize_metadata(m) for m in metas]
collection.add(ids=ids, metadatas=metas)
else:
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Expected metadata list value for key '{key}' to contain only
- Expected metadata value to be a str, int, float, bool, Spars
- Expected metadata list value for key '{key}' to be non-empty
- Expected metadata to be a dict or None, got {type(metadata).
- Expected metadata to be a non-empty dict, got {len(metadata)
AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16).
Data as JSON: /api/errors/685233eab5c94e3d.
Report an issue: GitHub.