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 aView on GitHub (pinned to aecdd12c8a)
Solutions
- Wrap single metadata in a list: metadatas=[{"source": "wiki"}] and keep one metadata per id/document/embedding.
- Check argument order if calling positionally: add(ids, embeddings, metadatas, documents) — a dict landing in metadatas is usually a shifted positional argument.
- If loading from JSON/pandas, coerce with list(df["meta"].map(dict)) or json.load then ensure the value is a list before passing.
- 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
- Always construct metadatas as a list comprehension parallel to ids: metadatas=[build_meta(i) for i in items].
- Assert len(metadatas) == len(ids) before every add/upsert — mismatched shapes usually accompany wrong types.
- Never pass a single dict; wrap in [ ] even for one record.
- Run chromadb.api.types.validate_metadatas directly in test suites for filter/ingestion builders.
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
- Expected metadata list value for key '{key}' to be non-empty
- Expected metadata list value for key '{key}' to contain only
- Expected metadata to be a dict or None, got {type(metadata).
- Expected metadata to be a non-empty dict, got {len(metadata)
- Expected metadata to not contain the reserved key {META_KEY_
AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16).
Data as JSON: /api/errors/ee1e09e9eb5b5836.
Report an issue: GitHub.