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
- Use supported numeric operators: $gt/$gte/$lt/$lte/$in/$ne/$nin and $exists
- Translate client-side sugar ($between) into `$gte` + `$lte` pairs in your query adapter
- Whitelist operators per field type in dynamic query builders before building the filter
- 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
- Translate client sugar ($between → $gte+$lte) server-side
- Whitelist operators per field type in query builders
- Fuzz-test dynamic filter endpoints with unknown operators
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
- Can't use ${$conditional} with Buffer.
- Can't use ${$conditional} with Date.
- Query filter must be an object, got an array ${util.inspect(
- Cast to number failed for value "${value}" (type ${valueType
- Invalid addFields() argument. Must be an object
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/1b07bf1be09113d6.
Report an issue: GitHub.