Automattic/mongoose · error · MongooseError

Can't use ${conditional} with Number.

Error message

Can't use ${conditional} with Number.

What it means

SchemaNumber.castForQuery throws a MongooseError when a filter uses an operator with no registered handler for Number paths. Number supports the base SchemaType handlers ($in, $nin, $ne, $exists, ...) plus $gt/$gte/$lt/$lte; other operators — commonly $regex, $all, $mod-style leftovers, typos, or non-MongoDB operators from client-side libraries — hit the guard.

Source

Thrown at lib/schema/number.js:475

Object.defineProperty(SchemaNumber.prototype, '$conditionalHandlers', {
  enumerable: false,
  value: $conditionalHandlers
});

/**
 * Casts contents for queries.
 *
 * @param {string} $conditional
 * @param {any} [value]
 * @api private
 */

SchemaNumber.prototype.castForQuery = function($conditional, val, context) {
  let handler;
  if ($conditional != null) {
    handler = this.$conditionalHandlers[$conditional];
    if (!handler) {
      throw new MongooseError('Can\'t use ' + $conditional + ' with Number.');
    }
    return handler.call(this, val, context);
  }

  try {
    val = this.applySetters(val, context);
  } catch (err) {
    if (err instanceof CastError && err.path === this.path && this.$fullPath != null) {
      err.path = this.$fullPath;
    }
    throw err;
  }

  return val;
};

/**
 * Returns this schema type's representation in a JSON schema.

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Use supported numeric operators: $gt/$gte/$lt/$lte/$in/$ne/$nin and $exists
  2. Translate client-side sugar ($between) into `$gte` + `$lte` pairs in your query adapter
  3. Whitelist operators per field type in dynamic query builders before building the filter
  4. Catch the MongooseError and return a 400 naming the rejected operator

Example fix

// before
Model.find({ price: { $between: [10, 20] } }); // sift-style operator

// after
Model.find({ price: { $gte: 10, $lte: 20 } });
Defensive patterns

Strategy: validation

Validate before calling

const NUMBER_OPS = new Set(['$gt','$gte','$lt','$lte','$in','$nin','$ne','$eq','$exists','$not','$cmp']);
function assertNumberOpsSafe(filter) {
  for (const [path, cond] of Object.entries(filter ?? {})) {
    if (path.startsWith('$') || !cond || typeof cond !== 'object') continue;
    for (const op of Object.keys(cond)) {
      if (op.startsWith('$') && !NUMBER_OPS.has(op)) throw new Error(`${op} not allowed on ${path}`);
    }
  }
}

Try / catch

try { await Model.find(filter); } catch (err) { if (/Can't use .* with Number/.test(err.message)) { throw Object.assign(new Error(err.message), { status: 400 }); } throw err; }

Prevention

When it happens

Trigger: `Model.find({ age: { $regex: '^2' } })`, `Model.find({ price: { $between: [10, 20] } })` (sift.js operator sent to the server), `{ qty: { $gtee: 1 } }` (typo), or any `{ <numberPath>: { $<op>: ... } }` with an unregistered $op.

Common situations: Generic admin/search UIs applying the same filter operators to every column; passing client-side query libraries' operator objects straight into Mongoose filters; typos and version drift where an operator was removed or renamed.

Related errors


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