{"record":{"id":"18ff98178b01e545","repo":"Automattic/mongoose","slug":"document-updateone-pre-hooks-cannot-overwrite-argu","errorCode":null,"errorMessage":"Document updateOne pre hooks cannot overwrite arguments","messagePattern":"Document updateOne pre hooks cannot overwrite arguments","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/document.js","lineNumber":907,"sourceCode":" * @param {boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.\n * @param {boolean|object} [options.middleware=true] set to `false` to skip all user-defined middleware\n * @param {boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks\n * @param {boolean} [options.middleware.post=true] set to `false` to skip only post hooks\n * @return {Query}\n * @api public\n * @memberOf Document\n * @instance\n */\n\nDocument.prototype.updateOne = function updateOne(update, options) {\n  const query = this.constructor.updateOne();\n  const self = this;\n  query.pre(async function queryPreUpdateOne() {\n    const res = await self._execDocumentPreHooks('updateOne', options, [self, update, options]);\n    // `self` is passed to pre hooks as argument for backwards compatibility, but that\n    // isn't the actual arguments passed to the wrapped function.\n    if (res[0] !== self || res[1] !== update || res[2] !== options) {\n      throw new Error('Document updateOne pre hooks cannot overwrite arguments');\n    }\n    query.updateOne({ _id: self._doc._id }, update, options);\n    // Apply custom where conditions _after_ document updateOne middleware for\n    // consistency with save() - sharding plugin needs to set $where\n    if (self.$where != null) {\n      this.where(self.$where);\n    }\n    if (self.$session() != null) {\n      if (!('session' in query.options)) {\n        query.options.session = self.$session();\n      }\n    }\n    return res;\n  });\n  query.post(function queryPostUpdateOne() {\n    return self._execDocumentPostHooks('updateOne', options);\n  });\n","sourceCodeStart":889,"sourceCodeEnd":925,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/document.js#L889-L925","documentation":"Document#updateOne builds a query and first replays document-level pre('updateOne') hooks, passing them (doc, update, options). After the hooks run, Mongoose asserts those three arguments are still the identical values. A hook that replaces an argument - a legacy function-style hook calling next() with new args, or an async hook returning replacement values - fails the assertion, because the wrapped update must execute with the arguments the caller originally passed.","triggerScenarios":"A `schema.pre('updateOne', { document: true, query: false })` hook that passes new arguments onward (function-style `next(modifiedUpdate)`), returns a replacement arguments array from an async hook, or reassigns/swaps the `update` or `options` objects instead of mutating them in place.","commonSituations":"Old Mongoose 5/6 middleware that modified updateOne args via next() being run on Mongoose 8.x where this guard exists; shared plugins that 'normalize' update objects by building and returning a new object; upgrade churn after the updateOne middleware refactor.","solutions":["Mutate the `update`/`options` objects in place inside the hook (e.g. `update.$set = { ...update.$set, updatedAt: new Date() }`) so the references stay identical","If the update must be rewritten, use query middleware: pre('updateOne', { document: false, query: true }) with this.getUpdate()/this.setUpdate()","Make async pre hooks return nothing so arguments pass through untouched"],"exampleFix":"// before\nschema.pre('updateOne', { document: true, query: false }, async function(_doc, update) {\n  return { $set: { ...update.$set, updatedAt: new Date() } }; // replaces the update argument -> throws\n});\n\n// after\nschema.pre('updateOne', { document: true, query: false }, async function(_doc, update) {\n  update.$set = { ...update.$set, updatedAt: new Date() }; // same object reference; mutation is allowed\n});","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await doc.updateOne({ $set: patch });\n} catch (err) {\n  if (err.message === 'Document updateOne pre hooks cannot overwrite arguments') {\n    // one of your pre('updateOne') document hooks replaced its arguments;\n    // fix it to mutate update/options in place instead\n  } else { throw err; }\n}","preventionTips":["In document pre('updateOne') hooks, only mutate the passed update/options objects; never reassign or return replacements","Use query middleware (document: false, query: true) when the update itself must be rewritten","After upgrading Mongoose majors, grep pre('updateOne') hooks for next() calls that pass arguments"],"tags":["mongoose","middleware","hooks","updateone","document"],"backgroundTag":"middleware-argument-overwrite","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}