{"record":{"id":"dfa4f8fc1da509e7","repo":"chroma-core/chroma","slug":"expected-where-to-have-exactly-one-operator-but","errorCode":null,"errorMessage":"Expected 'where' to have exactly one operator, but got ${Object.keys(where).length}","messagePattern":"Expected 'where' to have exactly one operator, but got (.+?)","errorType":"validation","errorClass":"ChromaValueError","httpStatus":null,"severity":"error","filePath":"clients/new-js/packages/chromadb/src/utils.ts","lineNumber":539,"sourceCode":"  if (recordSetLength > maxBatchSize) {\n    throw new ChromaValueError(\n      `Record set length ${recordSetLength} exceeds max batch size ${maxBatchSize}`,\n    );\n  }\n};\n\n/**\n * Validates a where clause for metadata filtering.\n * @param where - Where clause object to validate\n * @throws ChromaValueError if the where clause is malformed\n */\nexport const validateWhere = (where: Where) => {\n  if (typeof where !== \"object\") {\n    throw new ChromaValueError(\"Expected where to be a non-empty object\");\n  }\n\n  if (Object.keys(where).length != 1) {\n    throw new ChromaValueError(\n      `Expected 'where' to have exactly one operator, but got ${\n        Object.keys(where).length\n      }`,\n    );\n  }\n\n  Object.entries(where).forEach(([key, value]) => {\n    if (\n      key !== \"$and\" &&\n      key !== \"$or\" &&\n      key !== \"$in\" &&\n      key !== \"$nin\" &&\n      ![\"string\", \"number\", \"boolean\", \"object\"].includes(typeof value)\n    ) {\n      throw new ChromaValueError(\n        `Expected 'where' value to be a string, number, boolean, or an operator expression, but got ${value}`,\n      );\n    }","sourceCodeStart":521,"sourceCodeEnd":557,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/clients/new-js/packages/chromadb/src/utils.ts#L521-L557","documentation":"After the type gate, validateWhere requires exactly one key at the top level of the where object. Chroma's filter grammar allows a single field comparison or a single logical operator ($and/$or) per level; two or more sibling keys such as { a: 1, b: 2 } throw ChromaValueError. Multiple conditions must be combined explicitly with $and or $or.","triggerScenarios":"where: { genre: 'sci-fi', year: 2020 } (two fields at top level). Building filters by Object.assign of several single-field filters. where: {} fails the same check with 0 keys.","commonSituations":"Coming from SQL (WHERE a AND b) or Mongo (where multiple top-level keys mean AND); composing filters from user-selected facets by merging objects.","solutions":["Wrap multiple conditions: where: { $and: [{ genre: 'sci-fi' }, { year: 2020 }] }.","Compose facet filters by pushing into a $and array instead of merging objects.","Ensure the object has exactly one top-level key; use $and's array grammar for everything else."],"exampleFix":"// before\nwhere: { genre: 'sci-fi', year: 2020 }\n\n// after\nwhere: { $and: [{ genre: 'sci-fi' }, { year: 2020 }] }","handlingStrategy":"validation","validationCode":"const combine = (clauses) => clauses.length === 1 ? clauses[0] : { $and: clauses };\nconst where = combine([{ genre: 'sci-fi' }, { year: 2020 }]);\nawait collection.query({ queryTexts, where });","typeGuard":"const isSingleKey = (w: object) => Object.keys(w).length === 1;","tryCatchPattern":"try {\n  await collection.query({ queryTexts, where });\n} catch (e) {\n  if ((e as Error).message.includes('exactly one operator')) {\n    // rebuild as { $and: [...] } from the sibling keys and retry\n  } else {\n    throw e;\n  }\n}","preventionTips":["Never merge filter objects with Object.assign/spread — push clauses into a $and array.","Remember Chroma allows exactly one top-level key per where level.","Use a filter-builder helper that enforces the grammar."],"tags":["where-filter","validation","query"],"backgroundTag":"invalid-where-clause","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}