Automattic/mongoose · error · MongooseError
Query.prototype.exec() no longer accepts a callback
Error message
Query.prototype.exec() no longer accepts a callback
What it means
Mongoose 7 removed callback support from all query methods, and Query.prototype.exec() now throws if its first argument is a function or a function is passed as a second argument. exec() is async and returns a promise; the old `query.exec('update', callback)` and `query.exec(callback)` patterns are hard errors rather than deprecation warnings.
Source
Thrown at lib/query.js:4746
return isPathSelectedInclusive(this._fields, path);
};
/**
* Executes the query
*
* #### Example:
*
* const promise = query.exec();
* const promise = query.exec('update');
*
* @param {string|Function} [operation]
* @return {Promise}
* @api public
*/
Query.prototype.exec = async function exec(op) {
if (typeof op === 'function' || (arguments.length >= 2 && typeof arguments[1] === 'function')) {
throw new MongooseError('Query.prototype.exec() no longer accepts a callback');
}
this._validateOp();
if (typeof op === 'string') {
this.op = op;
}
if (this.op == null) {
throw new MongooseError('Query must have `op` before executing');
}
if (this.model == null) {
throw new MongooseError('Query must have an associated model before executing');
}
const thunk = opToThunk.get(this.op);
if (!thunk) {
throw new MongooseError('Query has invalid `op`: "' + this.op + '"');
}View on GitHub (pinned to 49cdab0136)
Solutions
- Replace `query.exec(cb)` with `await query.exec()` or `query.exec().then(...)`.
- Replace `query.exec('find', cb)` with `query.find(); await query.exec()` or `query.find().exec()` (chainable exec takes no op).
- Use the Mongoose 7 migration guide and grep for `exec(function` / `exec(cb` to find every remaining site.
- If you must keep a callback API for downstream code, wrap the promise yourself in one adapter instead of passing callbacks to Mongoose.
Example fix
// before (Mongoose 6)
Model.find({}).sort({ name: 1 }).exec((err, docs) => { ... });
// after (Mongoose 7+)
const docs = await Model.find({}).sort({ name: 1 }).exec(); Defensive patterns
Strategy: type-guard
Validate before calling
if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') { throw new TypeError('exec() is promise-only in Mongoose 7+'); } Type guard
const argsContainCallback = (...args) => args.some(a => typeof a === 'function');
Try / catch
try { await query.exec(); } catch (err) { if (err instanceof mongoose.Error && /no longer accepts a callback/.test(err.message)) { /* strip callback args, switch to await */ } throw err; } Prevention
- Grep for 'exec(' plus function args during Mongoose 7 upgrades.
- Use async/await consistently for all queries.
- Wrap callback-style consumers behind one promise adapter.
When it happens
Trigger: `query.exec(callback)`, `query.exec('find', callback)`, or `.then()`-less callback chains left over from Mongoose <=6; libraries that probe arguments and forward callbacks into exec.
Common situations: Upgrading an app from Mongoose 5/6 to 7+; legacy codebases with hundreds of `exec(cb)` sites; old middleware that wraps exec with a callback interface.
Related errors
- Model.init() no longer accepts a callback
- Model.createCollection() no longer accepts a callback
- Model.syncIndexes() no longer accepts a callback
- Model.cleanIndexes() no longer accepts a callback
- Model.listIndexes() no longer accepts a callback
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/70ab1c23ba82265d.
Report an issue: GitHub.