chroma-core/chroma · error · Error

Operator dictionary for field "${field}" must contain exactl

Error message

Operator dictionary for field "${field}" must contain exactly one operator

What it means

When a field's value in a where dict is a plain object, the parser treats it as an operator dictionary that must contain exactly one operator key. Two operators on the same field in one dict — typically a range like { price: { $gte: 10, $lte: 100 } } — are rejected, as is an empty operator object { field: {} }. Split the operators into separate clauses joined by $and.

Source

Thrown at clients/new-js/packages/chromadb/src/execution/expression/where.ts:223

      .reduce(
        (acc, condition) => OrWhere.combine(acc, condition),
        conditions[0],
      );
  }

  const entries = Object.entries(data);
  if (entries.length !== 1) {
    throw new Error("Where dictionary must contain exactly one field");
  }

  const [field, value] = entries[0];
  if (!isPlainObject(value)) {
    return new ComparisonWhere(field, "$eq", value);
  }

  const operatorEntries = Object.entries(value);
  if (operatorEntries.length !== 1) {
    throw new Error(
      `Operator dictionary for field "${field}" must contain exactly one operator`,
    );
  }

  const [operator, operand] = operatorEntries[0];
  const factory = comparisonOperatorMap.get(operator);
  if (!factory) {
    throw new Error(`Unsupported where operator: ${operator}`);
  }

  return factory(field, operand);
};

export const createComparisonWhere = (
  key: string,
  operator: string,
  value: unknown,
): WhereExpression => new ComparisonWhere(key, operator, value);

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Split the range: { $and: [{ price: { $gte: 10 } }, { price: { $lte: 100 } }] }.
  2. Or use the builder: WhereExpression.from({ price: { $gte: 10 } }).and({ price: { $lte: 100 } }).
  3. If the two operators were meant as alternatives, join them with $or instead.
  4. Guard against empty operator dicts when generating filters dynamically.

Example fix

// before
where: { price: { $gte: 10, $lte: 100 } }

// after
where: { $and: [{ price: { $gte: 10 } }, { price: { $lte: 100 } }] }
Defensive patterns

Strategy: validation

Validate before calling

function rangeFilter(field: string, min: number, max: number) {
  return { $and: [{ [field]: { $gte: min } }, { [field]: { $lte: max } }] };
}

Type guard

function isSingleOperatorDict(value: unknown): boolean {
  if (typeof value !== 'object' || value === null || Array.isArray(value)) return true;
  return Object.keys(value).length === 1;
}

Try / catch

try {
  await collection.query({ where });
} catch (e) {
  if (e instanceof Error && e.message.includes('must contain exactly one operator')) {
    // split the field's operator dict into $and-joined single-operator clauses
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Range filters: where: { price: { $gte: 10, $lte: 100 } }; time windows: { ts: { $gt: start, $lt: end } }; two equality-ish operators in one dict: { status: { $eq: 'a', $ne: 'b' } }; { field: {} } built when an operator/value lookup came back empty.

Common situations: Porting MongoDB range queries that allow multiple operators per field; date/time-window filters; price slider UIs; filter builders that merge operator dicts with spread.

Related errors


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