chroma-core/chroma · error · ChromaValueError

Expected operand value to be a string, number, or boolean fo

Error message

Expected operand value to be a string, number, or boolean for ${operator}, but got ${typeof operand}

What it means

$contains and $not_contains accept only scalar string, number, or boolean operands; validateWhere throws for arrays, objects, null, or undefined. These operators do substring matching against a single value — a list of needles is not supported and must be expanded into multiple clauses.

Source

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

        ["$gt", "$gte", "$lt", "$lte"].includes(operator) &&
        typeof operand !== "number"
      ) {
        throw new ChromaValueError(
          `Expected operand value to be a number for ${operator}, but got ${typeof operand}`,
        );
      }

      if (["$in", "$nin"].includes(operator) && !Array.isArray(operand)) {
        throw new ChromaValueError(
          `Expected operand value to be an array for ${operator}, but got ${operand}`,
        );
      }

      if (
        ["$contains", "$not_contains"].includes(operator) &&
        !["string", "number", "boolean"].includes(typeof operand)
      ) {
        throw new ChromaValueError(
          `Expected operand value to be a string, number, or boolean for ${operator}, but got ${typeof operand}`,
        );
      }

      if (
        ![
          "$gt",
          "$gte",
          "$lt",
          "$lte",
          "$ne",
          "$eq",
          "$in",
          "$nin",
          "$contains",
          "$not_contains",
        ].includes(operator)
      ) {

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Use one scalar per clause and combine: { $or: [{ title: { $contains: 'ai' } }, { title: { $contains: 'ml' } }] }.
  2. Skip the clause when the term is null/undefined.
  3. For document text filtering use whereDocument's $contains instead of a metadata clause.

Example fix

// before
where: { title: { $contains: ['ai', 'ml'] } }

// after
where: { $or: [{ title: { $contains: 'ai' } }, { title: { $contains: 'ml' } }] }
Defensive patterns

Strategy: validation

Validate before calling

const isScalar = (v) => ['string', 'number', 'boolean'].includes(typeof v);
const containsAny = (field, terms) => terms.length === 1
  ? { [field]: { $contains: terms[0] } }
  : { $or: terms.map(t => ({ [field]: { $contains: t } })) };
await collection.query({ queryTexts, where: containsAny('title', ['ai', 'ml']) });

Type guard

const isContainsOperand = (v: unknown): v is string | number | boolean =>
  ['string', 'number', 'boolean'].includes(typeof v);

Try / catch

try {
  await collection.query({ queryTexts, where });
} catch (e) {
  if ((e as Error).message.includes('boolean for $contains')) {
    // expand the list into $or clauses of scalar $contains and retry
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: where: { title: { $contains: ['ai', 'ml'] } } — array operand. { $contains: null }. Passing a regex object as the needle.

Common situations: Trying to express multi-keyword search with one $contains; optional search terms defaulting to null; assuming regex support.

Related errors


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