Automattic/mongoose · error · Error
Document updateOne pre hooks cannot overwrite arguments
Error message
Document updateOne pre hooks cannot overwrite arguments
What it means
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.
Source
Thrown at lib/document.js:907
* @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.
* @param {boolean|object} [options.middleware=true] set to `false` to skip all user-defined middleware
* @param {boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
* @param {boolean} [options.middleware.post=true] set to `false` to skip only post hooks
* @return {Query}
* @api public
* @memberOf Document
* @instance
*/
Document.prototype.updateOne = function updateOne(update, options) {
const query = this.constructor.updateOne();
const self = this;
query.pre(async function queryPreUpdateOne() {
const res = await self._execDocumentPreHooks('updateOne', options, [self, update, options]);
// `self` is passed to pre hooks as argument for backwards compatibility, but that
// isn't the actual arguments passed to the wrapped function.
if (res[0] !== self || res[1] !== update || res[2] !== options) {
throw new Error('Document updateOne pre hooks cannot overwrite arguments');
}
query.updateOne({ _id: self._doc._id }, update, options);
// Apply custom where conditions _after_ document updateOne middleware for
// consistency with save() - sharding plugin needs to set $where
if (self.$where != null) {
this.where(self.$where);
}
if (self.$session() != null) {
if (!('session' in query.options)) {
query.options.session = self.$session();
}
}
return res;
});
query.post(function queryPostUpdateOne() {
return self._execDocumentPostHooks('updateOne', options);
});
View on GitHub (pinned to 49cdab0136)
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
Example fix
// before
schema.pre('updateOne', { document: true, query: false }, async function(_doc, update) {
return { $set: { ...update.$set, updatedAt: new Date() } }; // replaces the update argument -> throws
});
// after
schema.pre('updateOne', { document: true, query: false }, async function(_doc, update) {
update.$set = { ...update.$set, updatedAt: new Date() }; // same object reference; mutation is allowed
}); Defensive patterns
Strategy: try-catch
Try / catch
try {
await doc.updateOne({ $set: patch });
} catch (err) {
if (err.message === 'Document updateOne pre hooks cannot overwrite arguments') {
// one of your pre('updateOne') document hooks replaced its arguments;
// fix it to mutate update/options in place instead
} else { throw err; }
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Document deleteOne pre hooks cannot overwrite arguments
- Aggregate `near()` argument must have a `near` property
- Aggregate `near()` argument has invalid coordinates, got "${
- Parameter "obj" to Document() must be an object, got "${obj}
- Parameter "doc" to init() must be an object, got "${doc}" (t
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/18ff98178b01e545.
Report an issue: GitHub.