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
- Await it: `const doc = await User.findOneAndUpdate({ _id }, { $set: { name } }, { new: true });`
- Use `.orFail()` or check for null instead of the old err-first null pattern
- If you relied on the raw result, pass `includeResultMetadata: true` and read `.value`/`.lastErrorObject`
- 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
- Decide result handling up front: default resolves the document (null if none); use includeResultMetadata for the raw result
- Use .orFail() where the old code treated 'not found' as an error
- TypeScript catches callback calls here at compile time — migrate types first, then runtime code
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
- Model.find() no longer accepts a callback
- Model.findById() no longer accepts a callback
- Model.findOne() no longer accepts a callback
- Model.init() no longer accepts a callback
- Model.createCollection() no longer accepts a callback
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/b4e11257b04cb62f.
Report an issue: GitHub.