What it means
$inc adds to a number, and $mul multiplies one. MongoDB keeps each value’s type, so the string
"12" isn’t a number to it, and neither is null. When the field in a matched document holds one
of those, the server refuses the update with code 14, TypeMismatch. The message names the
document’s _id, the field and the type it found:
Cannot apply $inc to a value of non-numeric type. {_id: "about"} has the field 'views' of non-numeric type string
A missing field is fine: $inc creates it with the amount. The other direction fails too: an
amount that isn’t a number gives Cannot increment with non-numeric argument: {views: "1"}, with
the same code.
Common causes
- Numbers stored as strings, from a CSV import, a form, or JSON where the number was quoted.
nullinstead of missing: a schema default or an insert that sets the counter tonull.- An amount that’s a string:
$inc: { views: req.body.step }wherestepis"1". - The wrong field:
$incon an array or an object, or a path that points at one. updateManyover mixed data: most documents are fine, a few old ones aren’t, and the update stops at the first bad one.
How to fix it
Find the documents that aren’t numbers
db.counters.find({ views: { $exists: true, $not: { $type: "number" } } })
$type: "number" matches every numeric type (int, long, double, decimal). Without $exists, the
query also lists documents that have no views at all, which $inc handles fine.
Convert strings to numbers
A pipeline update can convert in place:
db.counters.updateMany(
{ views: { $type: "string" } },
[{ $set: { views: { $toInt: "$views" } } }]
)
Use $toDouble or $toDecimal for values with decimals. If some strings aren’t numbers at all, use
$convert with onError so one bad value doesn’t stop the update:
{ $convert: { input: "$views", to: "int", onError: 0 } }.
Replace null
db.counters.updateMany({ views: null }, { $set: { views: 0 } })
{ views: null } matches both null and a missing field, so this also sets the missing ones to 0.
Send numbers from your code
Convert request values before the update (Number(req.body.step)), and check the result isn’t
NaN. To stop strings coming back, add a validator that requires a number:
db.runCommand({
collMod: "counters",
validator: { $jsonSchema: { properties: { views: { bsonType: ["int", "long", "double", "decimal"] } } } }
})
A write that breaks it then fails with Document failed validation instead.
Know that updateMany can stop half-way
updateMany isn’t all-or-nothing: documents updated before the bad one stay updated. After fixing
the data, rerunning an $inc would count those twice. Fix the data first, or run the increment in
a transaction on a replica set.
Reproduce it
MongoDB 8.0.32, mongosh 2.12.0, four counters:
db.counters.insertMany([
{ _id: "home", views: 10 },
{ _id: "about", views: "12" },
{ _id: "blog", views: null },
{ _id: "shop" }
])
db.counters.updateMany({}, { $inc: { views: 1 } })
MongoServerError: Cannot apply $inc to a value of non-numeric type. {_id: "about"} has the field 'views' of non-numeric type string
The error’s code was 14. Afterwards home had 11 views: it was updated before the update reached
about. $inc on blog alone named type null, on an array type array, and $mul on a string
field gave Cannot apply $mul to a value of non-numeric type. A string amount:
MongoServerError: Cannot increment with non-numeric argument: {views: "1"}
After the $toInt update and setting null to 0 (which also set shop), the same $inc matched
and modified all four documents.
In Inlet
Inlet shows each document in the inspector and keeps each value’s type when you edit it, so a
number you edit stays a number. Queries use mongosh syntax, so the $type query above lists the
documents to fix, and the conversion runs in a query tab.