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
- Omit the key entirely instead of setting null/undefined: delete update.$inc.count when the value is nullish
- Convert null to 0 when you mean 'no change': { $inc: { count: value ?? 0 } }
- Use $set or $unset when you actually intend to clear the field
- 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
- Treat null increments as 'no change': omit the key or use ?? 0
- Use $set/$unset when the intent is to clear a field
- Sanitize request bodies before mapping them onto update operators
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
- Cast to number failed for value "${value}" (type ${valueType
- a circular reference in the update value, updateValue: ${uti
- Invalid atomic update value for ${op}. Expected an object, r
- Invalid update pipeline operator: "${op}"
- Field `${pathToCheck}` is not in schema and strict mode is s
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/3c962f55106a23b2.
Report an issue: GitHub.