{"record":{"id":"812698e19129c6e3","repo":"Automattic/mongoose","slug":"cast-to-number-failed-for-value-value-type-812698","errorCode":null,"errorMessage":"Cast to number failed for value \"${value}\" (type ${valueType}) at path \"${path}\"","messagePattern":"Cast to number failed for value \"(.+?)\" \\(type (.+?)\\) at path \"(.+?)\"","errorType":"exception","errorClass":"CastError","httpStatus":null,"severity":"error","filePath":"lib/helpers/query/castUpdate.js","lineNumber":571,"sourceCode":" * Casts `val` according to `schema` and atomic `op`.\n *\n * @param {SchemaType} schema\n * @param {object} val\n * @param {string} op the atomic operator ($pull, $set, etc)\n * @param {string} $conditional\n * @param {Query} context\n * @param {string} path\n * @api private\n */\n\nfunction castUpdateVal(schema, val, op, $conditional, context, path) {\n  if (!schema) {\n    // non-existing schema path\n    if (op in numberOps) {\n      try {\n        return castNumber(val);\n      } catch {\n        throw new CastError('number', val, path);\n      }\n    }\n    return val;\n  }\n\n  const cond = schema.$isMongooseArray\n    && op in castOps\n    && (utils.isObject(val) || Array.isArray(val));\n  if (cond && !overwriteOps[op]) {\n    // Cast values for ops that add data to MongoDB.\n    // Ensures embedded documents get ObjectIds etc.\n    let schemaArrayDepth = 0;\n    let cur = schema;\n    while (cur.$isMongooseArray) {\n      ++schemaArrayDepth;\n      cur = cur.embeddedSchemaType;\n    }\n    let arrayDepth = 0;","sourceCodeStart":553,"sourceCodeEnd":589,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/helpers/query/castUpdate.js#L553-L589","documentation":"Mongoose throws this CastError when an update applies a numeric-only update operator ($inc or $pop) to a path that is not defined in the schema and the value cannot be converted to a number. Even for schema-less paths (schema is null in castUpdateVal), Mongoose still runs castNumber for these operators so that garbage never reaches MongoDB, and wraps the failure in a CastError with the raw value, its JS type, and the offending path.","triggerScenarios":"Model.updateOne()/updateMany()/findOneAndUpdate() with { $inc: { notInSchema: 'abc' } } or { $pop: { typoPath: 'x' } } on a schema with strict: false (with default strict mode the unknown path is stripped before reaching this cast). Typical shape: dynamically building $inc objects from user input where a non-numeric string sneaks in.","commonSituations":"Schemas with strict:false that accept arbitrary fields; typos in field names inside $inc updates; REST APIs forwarding request-body values straight into update operators; converting string values (form data, env vars, query params) without coercion.","solutions":["Pass a number (or numeric string) as the $inc/$pop value, e.g. { $inc: { [path]: Number(value) } }, and drop values that coerce to NaN","If the field is legitimate, define it in the schema (e.g. new Schema({ count: Number, ... }, { strict: false }))","Fix the typo so the path matches an existing schema path","Whitelist update paths and operators before handing user input to update operations"],"exampleFix":"// before\nawait Model.updateOne({ _id }, { $inc: { scroes: req.body.amount } }); // typo, not in schema\n\n// after\nawait Model.updateOne({ _id }, { $inc: { scores: Number(req.body.amount) } });","handlingStrategy":"validation","validationCode":"// Before updateOne with $inc/$pop from untrusted values\nfunction safeNumericUpdate(path, value) {\n  const n = Number(value);\n  if (value == null || !Number.isFinite(n)) {\n    throw new Error(`Invalid numeric operand for ${path}: ${JSON.stringify(value)}`);\n  }\n  return n;\n}\nawait Model.updateOne({ _id }, { $inc: { [path]: safeNumericUpdate(path, req.body.value) } });","typeGuard":"const isNumericOperand = (v) => v != null && Number.isFinite(Number(v));","tryCatchPattern":"try {\n  await Model.updateOne(filter, update);\n} catch (err) {\n  if (err instanceof mongoose.Error.CastError && err.kind === 'number') {\n    // log offending err.path / err.value and reject the request with 400\n  } else throw err;\n}","preventionTips":["Whitelist the exact paths allowed in $inc/$pop updates instead of forwarding user objects","Declare every incrementable field in the schema so casting happens against a SchemaType","Coerce and validate inputs (Number.isFinite) before building update objects"],"tags":["mongoose","update","cast-error","type-coercion","inc"],"backgroundTag":"type-cast-failed","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}