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

  1. Replace `query.exec(cb)` with `await query.exec()` or `query.exec().then(...)`.
  2. Replace `query.exec('find', cb)` with `query.find(); await query.exec()` or `query.find().exec()` (chainable exec takes no op).
  3. Use the Mongoose 7 migration guide and grep for `exec(function` / `exec(cb` to find every remaining site.
  4. 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

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


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