Automattic/mongoose · error · MongooseError

Model.findOneAndUpdate() no longer accepts a callback

Error message

Model.findOneAndUpdate() no longer accepts a callback

What it means

Model.findOneAndUpdate(conditions, update, options) returns a Query; Mongoose 7 removed the callback form, so a function in any of the first four argument slots throws synchronously. Note the result shape: by default the query resolves to the document (or null), and `includeResultMetadata: true` returns the driver's raw result.

Source

Thrown at lib/model.js:2430

 * @param {boolean} [options.new=false] if true, return the modified document rather than the original
 * @param {object|string} [options.fields] Field selection. Equivalent to `.select(fields).findOneAndUpdate()`
 * @param {number} [options.maxTimeMS] puts a time limit on the query - requires mongodb >= 2.6.0
 * @param {object|string} [options.sort] if multiple docs are found by the conditions, sets the sort order to choose which doc to update.
 * @param {boolean} [options.runValidators] if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema
 * @param {boolean} [options.setDefaultsOnInsert=true] If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created
 * @param {boolean} [options.includeResultMetadata] if true, returns the [raw result from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/7.0/interfaces/ModifyResult.html)
 * @param {boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
 * @param {boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
 * @return {Query}
 * @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html
 * @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
 * @api public
 */

Model.findOneAndUpdate = function(conditions, update, options) {
  _checkContext(this, 'findOneAndUpdate');
  if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
    throw new MongooseError('Model.findOneAndUpdate() no longer accepts a callback');
  }

  let fields;
  if (options) {
    fields = options.fields || options.projection;
  }

  const mq = new this.Query({}, {}, this, this.$__collection);
  mq.select(fields);

  return mq.findOneAndUpdate(conditions, update, options);
};

/**
 * Issues a mongodb findOneAndUpdate command by a document's _id field.
 * `findByIdAndUpdate(id, ...)` is equivalent to `findOneAndUpdate({ _id: id }, ...)`.
 *
 * Finds a matching document, updates it according to the `update` arg,

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Await it: `const doc = await User.findOneAndUpdate({ _id }, { $set: { name } }, { new: true });`
  2. Use `.orFail()` or check for null instead of the old err-first null pattern
  3. If you relied on the raw result, pass `includeResultMetadata: true` and read `.value`/`.lastErrorObject`
  4. Sweep `findOneAndUpdate\(` call sites for trailing function arguments

Example fix

// before
User.findOneAndUpdate({ _id: id }, { $set: { name } }, { new: true }, (err, doc) => {
  if (err) throw err;
  res.json(doc);
});

// after
const doc = await User.findOneAndUpdate({ _id: id }, { $set: { name } }, { new: true });
res.json(doc);
Defensive patterns

Strategy: validation

Validate before calling

const isFn = (a) => typeof a === 'function';
if ([conditions, update, options].some(isFn)) {
  throw new TypeError('findOneAndUpdate() is promise-only');
}
const doc = await User.findOneAndUpdate(conditions, update, options);

Prevention

When it happens

Trigger: `User.findOneAndUpdate({ _id }, { $set: { name } }, { new: true }, (err, doc) => ...)`; upsert helpers written callback-style; passing a callback in the `options` slot.

Common situations: Pre-Mongoose-7 upsert/patch handlers; confusion during upgrade because the old callback's `doc` is now the promise's resolved value (and null vs lastErrorObject semantics changed).

Related errors


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