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

  1. Move $or/$and/$nor to the filter's top level: `{ $or: [{ a: 1 }, { b: 2 }] }`.
  2. For per-field element clauses, express them with $elemMatch / $all + $elemMatch forms.
  3. 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

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


AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21). Data as JSON: /api/errors/f48d79e429240b5f. Report an issue: GitHub.