chroma-core/chroma · error · ValueError

Expected metadatas to be a list, got {metadatas}

Error message

Expected metadatas to be a list, got {metadatas}

What it means

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.

Source

Thrown at chromadb/api/types.py:1180

    Returns:
        Metadata dictionary with serialized SparseVectors converted to dataclass instances
    """
    if metadata is None:
        return None

    result: Dict[str, Any] = {}
    for key, value in metadata.items():
        if isinstance(value, dict) and value.get(TYPE_KEY) == SPARSE_VECTOR_TYPE_VALUE:
            result[key] = SparseVector.from_dict(value)
        else:
            result[key] = value
    return result


def validate_metadatas(metadatas: Metadatas) -> Metadatas:
    """Validates metadatas to ensure it is a list of dictionaries of strings to strings, ints, floats or bools"""
    if not isinstance(metadatas, list):
        raise ValueError(f"Expected metadatas to be a list, got {metadatas}")
    for metadata in metadatas:
        validate_metadata(metadata)
    return metadatas


def validate_where(where: Where) -> None:
    """
    Validates where to ensure it is a dictionary of strings to strings, ints, floats or operator expressions,
    or in the case of $and and $or, a list of where expressions
    """
    if not isinstance(where, dict):
        raise ValueError(f"Expected where to be a dict, got {where}")
    if len(where) != 1:
        raise ValueError(f"Expected where to have exactly one operator, got {where}")
    for key, value in where.items():
        if not isinstance(key, str):
            raise ValueError(f"Expected where key to be a str, got {key}")
        # $contains and $not_contains are only valid as operators within a

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Wrap single metadata in a list: metadatas=[{"source": "wiki"}] and keep one metadata per id/document/embedding.
  2. Check argument order if calling positionally: add(ids, embeddings, metadatas, documents) — a dict landing in metadatas is usually a shifted positional argument.
  3. If loading from JSON/pandas, coerce with list(df["meta"].map(dict)) or json.load then ensure the value is a list before passing.
  4. Never JSON-encode metadatas yourself; pass native Python objects.

Example fix

# before
collection.add(ids=["1"], documents=["hello"], metadatas={"src": "wiki"})
# after
collection.add(ids=["1"], documents=["hello"], metadatas=[{"src": "wiki"}])
Defensive patterns

Strategy: type-guard

Validate before calling

from typing import Any

def is_valid_metadatas(metadatas: Any) -> bool:
    if not isinstance(metadatas, list) or not metadatas:
        return False
    return all(
        isinstance(m, dict)
        and all(isinstance(k, str) and isinstance(v, (str, int, float, bool)) for k, v in m.items())
        for m in metadatas
    )

assert is_valid_metadatas(metadatas), "metadatas must be a list of flat str->scalar dicts"

Type guard

def is_metadatas(value: Any) -> TypeGuard[list[dict[str, str | int | float | bool]]]:
    return (
        isinstance(value, list)
        and all(isinstance(m, dict) for m in value)
        and all(
            isinstance(k, str) and isinstance(v, (str, int, float, bool))
            for m in value for k, v in m.items()
        )
    )

Try / catch

try:
    collection.add(ids=ids, documents=docs, metadatas=metadatas)
except ValueError as e:
    if "Expected metadatas to be a list" in str(e):
        metadatas = [metadatas] if isinstance(metadatas, dict) else metadatas
        raise  # or retry once with corrected shape
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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