Automattic/mongoose · error · MongooseError

Model.deleteMany() no longer accepts a callback

Error message

Model.deleteMany() no longer accepts a callback

What it means

Model.deleteMany(conditions, options) deletes all matching documents via a Query. As with every query static, Mongoose 7 dropped callback support: passing a function in any of the first three positional slots throws before the Query is even built.

Source

Thrown at lib/model.js:2083

 *     await Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }); // returns {deletedCount: x} where x is the number of documents deleted.
 *
 * #### Note:
 *
 * This function triggers `deleteMany` query hooks. Read the
 * [middleware docs](https://mongoosejs.com/docs/middleware.html#naming) to learn more.
 *
 * @param {object} conditions
 * @param {object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
 * @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.
 * @return {Query}
 * @api public
 */

Model.deleteMany = function deleteMany(conditions, options) {
  _checkContext(this, 'deleteMany');

  if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
    throw new MongooseError('Model.deleteMany() no longer accepts a callback');
  }

  const mq = new this.Query({}, {}, this, this.$__collection);
  mq.setOptions(options);

  return mq.deleteMany(conditions);
};

/**
 * Finds documents.
 *
 * Mongoose casts the `filter` to match the model's schema before the command is sent.
 * See our [query casting tutorial](https://mongoosejs.com/docs/tutorials/query_casting.html) for
 * more information on how Mongoose casts `filter`.
 *
 * #### Example:
 *
 *     // find all documents

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Await it: `const { deletedCount } = await User.deleteMany({ inactive: true });`
  2. Migrate error handling to try/catch around the await
  3. Sweep for `\.deleteMany\(` with function arguments repo-wide

Example fix

// before
User.deleteMany({ inactive: true }, (err) => { ... });

// after
const { deletedCount } = await User.deleteMany({ inactive: true });
Defensive patterns

Strategy: validation

Validate before calling

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

Prevention

When it happens

Trigger: `User.deleteMany({ inactive: true }, cb)`; cleanup jobs from the callback era; `User.deleteMany(cb)` intending delete-all.

Common situations: Post-migration leftovers; bulk-cleanup cron scripts that were never touched during the upgrade.

Related errors


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