Automattic/mongoose · error · MongooseError
Model.findByIdAndUpdate() no longer accepts a callback
Error message
Model.findByIdAndUpdate() no longer accepts a callback
What it means
Mongoose 7 removed callback support from all Model and Query APIs; findByIdAndUpdate() now returns a Query (thenable) only. The function inspects arguments[0] through arguments[3] and throws this MongooseError synchronously if any of them is a function. The check covers every position because a callback was previously allowed as the last argument (after id, update, options).
Source
Thrown at lib/model.js:2510
* @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 full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/7.0/interfaces/ModifyResult.html) rather than just the document
* @param {boolean} [options.upsert=false] if true, and no documents found, insert a new document
* @param {boolean} [options.new=false] if true, return the modified document rather than the original
* @param {object|string} [options.select] sets the document fields to return.
* @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 Model.findOneAndUpdate https://mongoosejs.com/docs/api/model.html#Model.findOneAndUpdate()
* @see mongodb https://www.mongodb.com/docs/manual/reference/command/findAndModify/
* @api public
*/
Model.findByIdAndUpdate = function(id, update, options) {
_checkContext(this, 'findByIdAndUpdate');
if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function' || typeof arguments[3] === 'function') {
throw new MongooseError('Model.findByIdAndUpdate() no longer accepts a callback');
}
// if a model is passed in instead of an id
if (id instanceof Document) {
id = id._doc._id;
}
return this.findOneAndUpdate.call(this, { _id: id }, update, options);
};
/**
* Issue a MongoDB `findOneAndDelete()` command.
*
* Finds a matching document, removes it, and returns the found document (if any).
*
* This function triggers the following middleware.
*
* - `findOneAndDelete()`View on GitHub (pinned to 49cdab0136)
Solutions
- Remove the callback and use await: const doc = await Model.findByIdAndUpdate(id, update, { new: true });, with try/catch for the error path
- Or chain .then()/.catch() on the returned Query: Model.findByIdAndUpdate(id, update).then(doc => ...).catch(err => ...)
- Grep the codebase for callback patterns during upgrade: rg "findByIdAndUpdate\\([^)]*,\\s*(function|\\([^)]*\\)\\s*=>)" and migrate every hit (Mongoose 6 to 7 migration guide has a checklist)
- If an immediate rewrite is impossible, temporarily pin mongoose@6 and schedule the migration; do not shim callbacks by wrapping the returned Query
- If using TypeScript, rely on the Mongoose 7+ type definitions: callback overloads were deleted, so tsc flags these call sites at compile time
Example fix
// before (Mongoose 6)
User.findByIdAndUpdate(id, { name: 'a' }, { new: true }, (err, doc) => {
if (err) return handleError(err);
console.log(doc);
});
// after (Mongoose 7/8)
try {
const doc = await User.findByIdAndUpdate(id, { name: 'a' }, { new: true });
console.log(doc);
} catch (err) {
handleError(err);
} Defensive patterns
Strategy: validation
Validate before calling
// Guard shared call sites / migration shims against callback args
function assertNoCallbacks(fnName, args) {
const i = args.findIndex(a => typeof a === 'function');
if (i !== -1) {
throw new TypeError(`${fnName}: arg ${i + 1} is a function; use await (Mongoose 7+ is promise-only)`);
}
}
// assertNoCallbacks('findByIdAndUpdate', [id, update, options]); Try / catch
try {
const doc = await Model.findByIdAndUpdate(id, update, { new: true });
} catch (err) {
if (err instanceof mongoose.MongooseError && /no longer accepts a callback/.test(err.message)) {
// legacy callback call site slipped through the migration
throw new Error('Migrate this call site to await/then', { cause: err });
}
throw err;
} Prevention
- Run the Mongoose 6 to 7 migration checklist and grep for ', function (' and arrow callbacks near Model method calls
- Use TypeScript: Mongoose 7+ typings have no callback overloads, so tsc flags every legacy call
- Standardize on async/await in lint rules (e.g. no-passing-callbacks custom rule / prefer-async) so callbacks cannot creep back
When it happens
Trigger: Calling Model.findByIdAndUpdate(id, update, options, callback) with Mongoose 6-style callback code, or Model.findByIdAndUpdate(id, update, callback) where the callback lands in the options slot (arguments[2]). Also triggered by argument-order bugs where a function is passed as id or update.
Common situations: Upgrading an app or a dependency from Mongoose 6 (or earlier) to Mongoose 7/8 without migrating call sites; copy-pasting code from old tutorials or Stack Overflow answers that use function (err, doc) {} style; shared middleware/helpers written for the callback API.
Related errors
- Model.findOneAndDelete() no longer accepts a callback
- Model.findByIdAndDelete() no longer accepts a callback
- Model.findOneAndReplace() no longer accepts a callback
- Model.create() no longer accepts a callback
- Model.insertMany() no longer accepts a callback
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/4cbcbc328794d5c9.
Report an issue: GitHub.