Automattic/mongoose · error · TypeError
conditional ${op} requires an array
Error message
conditional ${op} requires an array What it means
The handlers mongoose builds for logical operators ($or/$and/$nor) inside array-path query casting require the operator value to be an array of criteria objects; a scalar or bare object cannot express a disjunction/conjunction, so a TypeError is thrown during castForQuery.
Source
Thrown at lib/schema/array.js:637
* @memberOf SchemaArray
* @instance
* @api public
*/
const handle = SchemaArray.prototype.$conditionalHandlers = {};
handle.$all = cast$all;
handle.$options = String;
handle.$elemMatch = cast$elemMatch;
handle.$geoIntersects = geospatial.cast$geoIntersects;
handle.$or = createLogicalQueryOperatorHandler('$or');
handle.$and = createLogicalQueryOperatorHandler('$and');
handle.$nor = createLogicalQueryOperatorHandler('$nor');
function createLogicalQueryOperatorHandler(op) {
return function logicalQueryOperatorHandler(val, context) {
if (!Array.isArray(val)) {
throw new TypeError('conditional ' + op + ' requires an array');
}
const ret = [];
for (const obj of val) {
ret.push(cast(this.embeddedSchemaType.schema ?? context.schema, obj, null, this?.$$context));
}
return ret;
};
}
handle.$near =
handle.$nearSphere = geospatial.cast$near;
handle.$within =
handle.$geoWithin = geospatial.cast$within;
handle.$size =View on GitHub (pinned to 49cdab0136)
Solutions
- Move $or/$and/$nor to the filter's top level: `{ $or: [{ a: 1 }, { b: 2 }] }`.
- For per-field element clauses, express them with $elemMatch / $all + $elemMatch forms.
- Fix filter builders to always emit arrays of clauses for logical operators.
Example fix
// before
Model.find({ tags: { $or: { $eq: 'red' } } });
// after
Model.find({ $or: [{ tags: 'red' }, { tags: 'blue' }] }); Defensive patterns
Strategy: validation
Validate before calling
const LOGICAL = new Set(['$or', '$and', '$nor']);
const validateFilter = (filter) => {
for (const [k, v] of Object.entries(filter ?? {})) {
if (LOGICAL.has(k) && !Array.isArray(v)) throw new TypeError(`${k} requires an array of clauses`);
if (k.startsWith('$')) continue;
if (v && typeof v === 'object' && !Array.isArray(v)) validateFilter(v);
}
};
validateFilter(query); Prevention
- In query-builder code, always wrap logical clauses in arrays, even a single one ([clause]).
- Unit-test generated filters against mongoose casting before shipping them.
- Keep $or/$and/$nor at the filter's top level unless you specifically need $elemMatch.
When it happens
Trigger: `{ arr: { $or: { x: 1 } } }` - a logical operator used per-field instead of at the top level; `{ tags: { $and: 'red' } }`; programmatic filter builders emitting a single object where an array of clauses is required.
Common situations: Converting top-level $or/$and into per-field filters by mistake; generic filter code wrapping criteria inconsistently; raw MongoDB shapes that tolerate field-level use but mongoose's caster does not.
Related errors
- Cast to Array failed for value "${value}" at path "${path}"
- Cast to Object failed for value "${value}" at path "${path}.
- Can't use ${$conditional} with Array.
- Invalid addFields() argument. Must be an object
- Query filter must be an object, got an array ${util.inspect(
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/f48d79e429240b5f.
Report an issue: GitHub.