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

  1. Mutate the `update`/`options` objects in place inside the hook (e.g. `update.$set = { ...update.$set, updatedAt: new Date() }`) so the references stay identical
  2. If the update must be rewritten, use query middleware: pre('updateOne', { document: false, query: true }) with this.getUpdate()/this.setUpdate()
  3. 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

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


AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21). Data as JSON: /api/errors/18ff98178b01e545. Report an issue: GitHub.