{"record":{"id":"1b07bf1be09113d6","repo":"Automattic/mongoose","slug":"can-t-use-conditional-with-number","errorCode":null,"errorMessage":"Can't use ${conditional} with Number.","messagePattern":"Can't use (.+?) with Number\\.","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/schema/number.js","lineNumber":475,"sourceCode":"Object.defineProperty(SchemaNumber.prototype, '$conditionalHandlers', {\n  enumerable: false,\n  value: $conditionalHandlers\n});\n\n/**\n * Casts contents for queries.\n *\n * @param {string} $conditional\n * @param {any} [value]\n * @api private\n */\n\nSchemaNumber.prototype.castForQuery = function($conditional, val, context) {\n  let handler;\n  if ($conditional != null) {\n    handler = this.$conditionalHandlers[$conditional];\n    if (!handler) {\n      throw new MongooseError('Can\\'t use ' + $conditional + ' with Number.');\n    }\n    return handler.call(this, val, context);\n  }\n\n  try {\n    val = this.applySetters(val, context);\n  } catch (err) {\n    if (err instanceof CastError && err.path === this.path && this.$fullPath != null) {\n      err.path = this.$fullPath;\n    }\n    throw err;\n  }\n\n  return val;\n};\n\n/**\n * Returns this schema type's representation in a JSON schema.","sourceCodeStart":457,"sourceCodeEnd":493,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/schema/number.js#L457-L493","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","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"],"exampleFix":"// before\nModel.find({ price: { $between: [10, 20] } }); // sift-style operator\n\n// after\nModel.find({ price: { $gte: 10, $lte: 20 } });","handlingStrategy":"validation","validationCode":"const NUMBER_OPS = new Set(['$gt','$gte','$lt','$lte','$in','$nin','$ne','$eq','$exists','$not','$cmp']);\nfunction assertNumberOpsSafe(filter) {\n  for (const [path, cond] of Object.entries(filter ?? {})) {\n    if (path.startsWith('$') || !cond || typeof cond !== 'object') continue;\n    for (const op of Object.keys(cond)) {\n      if (op.startsWith('$') && !NUMBER_OPS.has(op)) throw new Error(`${op} not allowed on ${path}`);\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":"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; }","preventionTips":["Translate client sugar ($between → $gte+$lte) server-side","Whitelist operators per field type in query builders","Fuzz-test dynamic filter endpoints with unknown operators"],"tags":["mongoose","number","query","operator","filter"],"backgroundTag":"unsupported-query-operator","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}