chroma-core/chroma · error · ChromaValueError

Expected metadata value for key '${key}' to be a string, num

Error message

Expected metadata value for key '${key}' to be a string, number, boolean, SparseVector, typed array (string[], number[], boolean[]), or null

What it means

The per-key loop in validateMetadata accepts only null/undefined, string, number, boolean, a SparseVector (detected by validateSparseVector), or an array (which is then list-validated). Any other value — plain objects, Dates, Maps, functions — throws ChromaValueError. This mirrors Chroma's metadata model: scalar primitives plus sparse vectors, nothing nested.

Source

Thrown at clients/new-js/packages/chromadb/src/utils.ts:305

  for (const [key, v] of Object.entries(metadata)) {
    if (
      v === null ||
      v === undefined ||
      typeof v === "string" ||
      typeof v === "number" ||
      typeof v === "boolean"
    ) {
      continue;
    }
    if (validateSparseVector(v)) {
      continue;
    }
    if (Array.isArray(v)) {
      validateMetadataListValue(key, v);
      continue;
    }
    throw new ChromaValueError(
      `Expected metadata value for key '${key}' to be a string, number, boolean, SparseVector, typed array (string[], number[], boolean[]), or null`,
    );
  }
};

const SPARSE_VECTOR_TYPE = "sparse_vector" as const;

type SerializedSparseVector = SparseVector & {
  "#type": typeof SPARSE_VECTOR_TYPE;
};

type SerializedMetadataValue =
  | boolean
  | number
  | string
  | SerializedSparseVector
  | SparseVector
  | Array<boolean>

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Flatten nested objects into dot- or underscore-separated keys, e.g. { 'source.url': 'x' }.
  2. Serialize Dates with toISOString() and objects with JSON.stringify() before the call.
  3. Keep rich structure in the document text and store only scalar primitives in metadata.

Example fix

// before
metadatas: [{ createdAt: new Date(), source: { lang: 'en' } }]

// after
metadatas: [{ createdAt: new Date().toISOString(), 'source.lang': 'en' }]
Defensive patterns

Strategy: type-guard

Validate before calling

const isMetadataValue = (v) =>
  v == null || ['string', 'number', 'boolean'].includes(typeof v) || Array.isArray(v);
const bad = Object.entries(meta).filter(([, v]) => !isMetadataValue(v));
if (bad.length) throw new Error(`Unsupported metadata keys: ${bad.map(([k]) => k)}`);

Type guard

type MetadataValue = string | number | boolean | null | string[] | number[] | boolean[];
const isMetadataValue = (v: unknown): v is MetadataValue =>
  v == null ||
  ['string', 'number', 'boolean'].includes(typeof v) ||
  Array.isArray(v);

Try / catch

try {
  await collection.add({ ids, metadatas });
} catch (e) {
  if ((e as Error).message.includes('SparseVector, typed array')) {
    // flatten/serialize the offending values and retry
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: metadatas: [{ source: { url: 'x' } }] (nested object). { createdAt: new Date() } (typeof 'object', not an array). A function or Map instance as a value.

Common situations: Trying to store structured JSON or timestamps directly in metadata; migrating from another vector DB that allowed object metadata; not realizing only string/number/boolean (plus sparse vectors) are supported.

Related errors


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