{"record":{"id":"a78b5df0b4e04ea7","repo":"Automattic/mongoose","slug":"invalid-atomic-update-value-for-op-expected-an","errorCode":null,"errorMessage":"Invalid atomic update value for ${op}. Expected an object, received ${typeof val}","messagePattern":"Invalid atomic update value for (.+?)\\. Expected an object, received (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/helpers/query/castUpdate.js","lineNumber":153,"sourceCode":"  // cast each value\n  i = ops.length;\n  while (i--) {\n    const op = ops[i];\n    val = ret[op];\n    hasDollarKey = hasDollarKey || op.startsWith('$');\n    if (val?.$__) {\n      val = val.toObject(internalToObjectOptions);\n      ret[op] = val;\n    }\n    if (val &&\n        typeof val === 'object' &&\n        !Buffer.isBuffer(val) &&\n        mongodbUpdateOperators.has(op)) {\n      walkUpdatePath(schema, val, op, options, context, filter);\n    } else {\n      const msg = 'Invalid atomic update value for ' + op + '. '\n          + 'Expected an object, received ' + typeof val;\n      throw new Error(msg);\n    }\n\n    if (op.startsWith('$') && utils.isEmptyObject(val)) {\n      delete ret[op];\n    }\n  }\n\n  if (utils.hasOwnKeys(ret) === false &&\n      options.upsert &&\n      utils.hasOwnKeys(filter)) {\n    // Trick the driver into allowing empty upserts to work around\n    // https://github.com/mongodb/node-mongodb-native/pull/2490\n    // Shallow clone to avoid passing defaults in re: gh-13962\n    return { $setOnInsert: { ...filter } };\n  }\n  return ret;\n};\n","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/helpers/query/castUpdate.js#L135-L171","documentation":"castUpdate requires each update operator's value to be a non-null object (and not a Buffer) — that object maps field paths to values. After $set-sugar normalization, if an operator in mongodbUpdateOperators ($set, $inc, $push, $pull, $unset, $currentDate, $min, $max, $mul, $rename, $setOnInsert, $addToSet, $pop, $pullAll, $bit) has a non-object value, Mongoose throws a plain Error telling you the received typeof. Documents are auto-converted with toObject() first, so only genuine non-objects reach the check.","triggerScenarios":"Model.updateOne(filter, { $inc: 1 }); { $set: 'name' }; { $push: Buffer.from(...) }; { $unset: '' }; { $set: null } — any operator whose value is a string, number, boolean, null/undefined, or Buffer.","commonSituations":"Flattening { $set: { count: 1 } } into { $set: 1 } when refactoring; passing a serialized update from an API body without parsing; reusing classic $unset string syntax ({ $unset: 'field' }) which is invalid — $unset needs { field: '' }.","solutions":["Give every operator an object of path→value pairs: { $inc: { count: 1 } }, { $unset: { field: '' } }","JSON.parse incoming update payloads and assert the shape before passing them to updateOne/updateMany/findOneAndUpdate","For replacement-style updates, omit operators entirely and pass the plain doc to Model.replaceOne / findOneAndReplace"],"exampleFix":"// before\nUser.updateOne({ _id }, { $inc: 1 });\n\n// after\nUser.updateOne({ _id }, { $inc: { count: 1 } });","handlingStrategy":"validation","validationCode":"const UPDATE_OPS = new Set(['$set','$unset','$inc','$dec','$mul','$min','$max','$rename','$setOnInsert','$push','$pull','$addToSet','$pop','$pullAll','$currentDate','$bit']);\nfunction assertUpdateShape(update) {\n  for (const [op, v] of Object.entries(update)) {\n    if (UPDATE_OPS.has(op) && (v == null || typeof v !== 'object' || Buffer.isBuffer(v))) {\n      throw new Error(`${op} value must be an object of { field: value }`);\n    }\n  }\n}","typeGuard":"type UpdateOp = Record<string, Record<string, unknown>>;\nfunction isShapedUpdate(u: unknown): u is UpdateOp {\n  return typeof u === 'object' && u !== null && !Array.isArray(u) &&\n    Object.entries(u).every(([k, v]) => !k.startsWith('$') || (typeof v === 'object' && v !== null && !Buffer.isBuffer(v)));\n}","tryCatchPattern":"try { await Model.updateOne(f, u); } catch (err) { if (/Invalid atomic update value/.test(err.message)) { /* the message names the op and typeof received — wrap the value in { field: value } */ } throw err; }","preventionTips":["Never pass scalars directly under an update operator","Parse and shape-check update payloads from APIs before use","Remember $unset classic form is { field: '' }, not a string"],"tags":["mongoose","update","query","validation"],"backgroundTag":"mongoose-invalid-update","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}