chroma-core/chroma · error · ChromaValueError

Expected 'whereDocument' to have exactly one operator, but g

Error message

Expected 'whereDocument' to have exactly one operator, but got ${whereDocument}

What it means

The JS/TS Chroma client validates every whereDocument filter locally before any request is sent (validateWhereDocument in utils.ts, called from validateGetRequest, prepareQuery and validateDelete in collection.ts). A whereDocument clause must be an object with exactly one top-level operator key, e.g. { $contains: 'hello' } or { $and: [...] }, matching the WhereDocument union in types.ts. When Object.keys(whereDocument).length is 0 or greater than 1, this ChromaValueError is thrown client-side and the request never leaves the process.

Source

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

      }
    }
  });
};

/**
 * Validates a where document clause for document content filtering.
 * @param whereDocument - Where document clause to validate
 * @throws ChromaValueError if the clause is malformed
 */
export const validateWhereDocument = (whereDocument: WhereDocument) => {
  if (typeof whereDocument !== "object") {
    throw new ChromaValueError(
      "Expected 'whereDocument' to be a non-empty object",
    );
  }

  if (Object.keys(whereDocument).length != 1) {
    throw new ChromaValueError(
      `Expected 'whereDocument' to have exactly one operator, but got ${whereDocument}`,
    );
  }

  const [operator, operand] = Object.entries(whereDocument)[0];
  if (
    ![
      "$contains",
      "$not_contains",
      "$matches",
      "$not_matches",
      "$regex",
      "$not_regex",
      "$and",
      "$or",
    ].includes(operator)
  ) {
    throw new ChromaValueError(

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Keep exactly one operator per whereDocument object and combine clauses with { $and: [clause1, clause2] } or { $or: [...] }
  2. Omit the whereDocument parameter entirely (undefined) when no document filter is needed instead of passing {}
  3. Type the argument as the package's WhereDocument union so the compiler rejects multi-key object literals

Example fix

// before
await col.query({ queryTexts: ['hi'], whereDocument: { $contains: 'foo', $not_contains: 'bar' } });

// after
await col.query({ queryTexts: ['hi'], whereDocument: { $and: [{ $contains: 'foo' }, { $not_contains: 'bar' }] } });
Defensive patterns

Strategy: validation

Validate before calling

const DOC_OPERATORS = ['$contains', '$not_contains', '$matches', '$not_matches', '$regex', '$not_regex', '$and', '$or'];
function isValidWhereDocument(w: unknown): boolean {
  if (typeof w !== 'object' || w === null) return false;
  const keys = Object.keys(w);
  if (keys.length !== 1 || !DOC_OPERATORS.includes(keys[0])) return false;
  return true;
}
// before each call:
if (whereDocument && !isValidWhereDocument(whereDocument)) throw new TypeError('bad whereDocument shape');

Type guard

import type { WhereDocument } from 'chromadb';
const DOC_OPS = ['$contains', '$not_contains', '$matches', '$not_matches', '$regex', '$not_regex', '$and', '$or'];
export const isWhereDocument = (w: unknown): w is WhereDocument =>
  typeof w === 'object' && w !== null && Object.keys(w).length === 1 && DOC_OPS.includes(Object.keys(w)[0]);

Try / catch

try {
  await col.query({ queryTexts, whereDocument });
} catch (e) {
  if (e instanceof Error && e.message.includes("whereDocument' to have exactly one operator")) {
    // rebuild as { $and: [clauseA, clauseB] } and retry
  } else throw e;
}

Prevention

When it happens

Trigger: collection.get({ whereDocument: {} }); collection.query({ queryTexts: [...], whereDocument: { $contains: 'a', $not_contains: 'b' } }) (two operator keys as siblings); collection.delete({ whereDocument: [] }) — an array is typeof 'object' with zero keys and lands here too.

Common situations: Dynamically composing filters with object spread so multiple operators end up as siblings; copying `where` syntax (which allows one key per metadata field) into whereDocument; passing an empty object as a placeholder when the filter is optional.

Related errors


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