Automattic/mongoose · error · CastError

Cast to number failed for value "${value}" (type ${valueType

Error message

Cast to number failed for value "${value}" (type ${valueType}) at path "${path}"

What it means

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.

Source

Thrown at lib/helpers/query/castUpdate.js:571

 * Casts `val` according to `schema` and atomic `op`.
 *
 * @param {SchemaType} schema
 * @param {object} val
 * @param {string} op the atomic operator ($pull, $set, etc)
 * @param {string} $conditional
 * @param {Query} context
 * @param {string} path
 * @api private
 */

function castUpdateVal(schema, val, op, $conditional, context, path) {
  if (!schema) {
    // non-existing schema path
    if (op in numberOps) {
      try {
        return castNumber(val);
      } catch {
        throw new CastError('number', val, path);
      }
    }
    return val;
  }

  const cond = schema.$isMongooseArray
    && op in castOps
    && (utils.isObject(val) || Array.isArray(val));
  if (cond && !overwriteOps[op]) {
    // Cast values for ops that add data to MongoDB.
    // Ensures embedded documents get ObjectIds etc.
    let schemaArrayDepth = 0;
    let cur = schema;
    while (cur.$isMongooseArray) {
      ++schemaArrayDepth;
      cur = cur.embeddedSchemaType;
    }
    let arrayDepth = 0;

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Pass a number (or numeric string) as the $inc/$pop value, e.g. { $inc: { [path]: Number(value) } }, and drop values that coerce to NaN
  2. If the field is legitimate, define it in the schema (e.g. new Schema({ count: Number, ... }, { strict: false }))
  3. Fix the typo so the path matches an existing schema path
  4. Whitelist update paths and operators before handing user input to update operations

Example fix

// before
await Model.updateOne({ _id }, { $inc: { scroes: req.body.amount } }); // typo, not in schema

// after
await Model.updateOne({ _id }, { $inc: { scores: Number(req.body.amount) } });
Defensive patterns

Strategy: validation

Validate before calling

// Before updateOne with $inc/$pop from untrusted values
function safeNumericUpdate(path, value) {
  const n = Number(value);
  if (value == null || !Number.isFinite(n)) {
    throw new Error(`Invalid numeric operand for ${path}: ${JSON.stringify(value)}`);
  }
  return n;
}
await Model.updateOne({ _id }, { $inc: { [path]: safeNumericUpdate(path, req.body.value) } });

Type guard

const isNumericOperand = (v) => v != null && Number.isFinite(Number(v));

Try / catch

try {
  await Model.updateOne(filter, update);
} catch (err) {
  if (err instanceof mongoose.Error.CastError && err.kind === 'number') {
    // log offending err.path / err.value and reject the request with 400
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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