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 "${schema.path}"

What it means

Mongoose rejects null and undefined as operands for the numeric update operators $inc and $pop before any casting is attempted (val == null check in castUpdateVal). These operators need an actual number to add or a 1/-1 pop direction, so a missing value is always a client-side bug. The CastError names the schema path being updated.

Source

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

      val = [val];
      ++arrayDepth;
    }

    let tmp = schema.applySetters(Array.isArray(val) ? val : [val], context);

    for (let i = 0; i < additionalNesting; ++i) {
      tmp = tmp[0];
    }
    return tmp;
  }

  if (op in noCastOps) {
    return val;
  }
  if (op in numberOps) {
    // Null and undefined not allowed for $pop, $inc
    if (val == null) {
      throw new CastError('number', val, schema.path);
    }
    if (op === '$inc') {
      // Support `$inc` with long, int32, etc. (gh-4283)
      return schema.castForQuery(
        null,
        val,
        context
      );
    }
    try {
      return castNumber(val);
    } catch {
      throw new CastError('number', val, schema.path);
    }
  }
  if (op === '$currentDate') {
    if (typeof val === 'object') {
      return { $type: val.$type };

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Omit the key entirely instead of setting null/undefined: delete update.$inc.count when the value is nullish
  2. Convert null to 0 when you mean 'no change': { $inc: { count: value ?? 0 } }
  3. Use $set or $unset when you actually intend to clear the field
  4. Strip null/undefined values from $inc/$pop objects before executing the update

Example fix

// before
const inc = { count: req.body.count }; // count may be null
await Model.updateOne({ _id }, { $inc: inc });

// after
const inc = {};
if (req.body.count != null) inc.count = Number(req.body.count);
await Model.updateOne({ _id }, { $inc: inc });
Defensive patterns

Strategy: validation

Validate before calling

// Strip nullish operands from numeric update operators
function cleanNumberOps(update) {
  for (const op of ['$inc', '$pop']) {
    if (!update[op]) continue;
    for (const k of Object.keys(update[op])) {
      if (update[op][k] == null) delete update[op][k];
    }
    if (Object.keys(update[op]).length === 0) delete update[op];
  }
  return update;
}

Type guard

const hasNumberOperand = (update, op) => Object.values(update[op] ?? {}).every(v => v != null && Number.isFinite(Number(v)));

Try / catch

try {
  await Model.updateOne(f, { $inc: { count } });
} catch (err) {
  if (err instanceof mongoose.Error.CastError && err.kind === 'number') {
    // treat as client error: missing/absent increment value
  } else throw err;
}

Prevention

When it happens

Trigger: Model.updateOne({}, { $inc: { count: null } }), { $pop: { items: undefined } }, or update objects built dynamically where a key survives with a null/undefined value (e.g. Object.fromEntries over req.body with explicit nulls, or pick() keeping empty form fields).

Common situations: Optional numeric form fields submitted empty and serialized as null; JSON APIs where clients send null for 'no change'; code that mixes $set semantics (null allowed) with $inc semantics (null forbidden); sparse test fixtures.

Related errors


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