chroma-core/chroma · error · TypeError

Expected metadata key to be a str, got {key} which is a {typ

Error message

Expected metadata key to be a str, got {key} which is a {type(key).__name__}

What it means

Metadata keys must be str. In the insert-path validator (validate_metadata, chromadb/api/types.py:1087) this is a TypeError - not a ValueError - raised before the value checks, so exception handlers catching only ValueError will miss it. Non-str keys arrive from hand-built dicts such as {1: 'a'}, from dict(zip(range(n), values)), or from lenient parsers (e.g. YAML) that keep JSON numeric keys numeric instead of stringifying them.

Source

Thrown at chromadb/api/types.py:1087

def validate_metadata(metadata: Metadata) -> Metadata:
    """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).__name__} as metadata"
        )
    if metadata is None:
        return metadata
    if len(metadata) == 0:
        raise ValueError(
            f"Expected metadata to be a non-empty dict, got {len(metadata)} metadata attributes"
        )
    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:

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Stringify keys when building the dict: {str(k): v for k, v in meta.items()}
  2. Catch TypeError as well as ValueError around add/upsert/modify in defensive wrappers
  3. Encode numeric codes as values (e.g. {'category_id': 3}) instead of keys

Example fix

# before
meta = dict(zip(codes, labels))  # {101: 'a', 102: 'b'}

# after
meta = {str(k): v for k, v in zip(codes, labels)}
Defensive patterns

Strategy: type-guard

Validate before calling

meta = {str(k) if not isinstance(k, str) else k: v for k, v in meta.items()}
collection.add(ids=ids, metadatas=[meta])

Type guard

def metadata_keys_are_str(meta) -> bool:
    return all(isinstance(k, str) for k in meta)

Try / catch

try:
    collection.add(ids=ids, metadatas=metas)
except TypeError as e:
    if 'metadata key to be a str' in str(e):  # insert path raises TypeError
        metas = [{str(k): v for k, v in m.items()} for m in metas]
        collection.add(ids=ids, metadatas=metas)
    else:
        raise

Prevention

When it happens

Trigger: metadatas=[{1: 'a'}]; metadatas=[{1.5: 'x'}]; keys built via dict(zip(codes, labels)) with integer codes; YAML-loaded metadata where '1:' parses to an int key.

Common situations: Mapping numeric enum/categorical codes to labels and using the code as key; dataframe conversions that keep numeric index keys; config or metadata files parsed by non-JSON parsers that preserve key types.

Related errors


AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16). Data as JSON: /api/errors/099bb140399fbcb3. Report an issue: GitHub.