Automattic/mongoose · error · MongooseError

Query.prototype.deleteMany() no longer accepts a callback

Error message

Query.prototype.deleteMany() no longer accepts a callback

What it means

Query.prototype.deleteMany(filter, options) throws when filter, options, or a third positional argument is a function, mirroring the Mongoose 7 callback removal across all query helpers. The error is thrown while building the query, so bulk-delete cron jobs and scripts from the callback era fail on their first call after upgrading.

Source

Thrown at lib/query.js:3323

 *
 * #### Example:
 *
 *     const res = await Character.deleteMany({ name: /Stark/, age: { $gte: 18 } });
 *     // `0` if no docs matched the filter, number of docs deleted otherwise
 *     res.deletedCount;
 *
 * @param {object|Query} [filter] mongodb selector
 * @param {object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
 * @param {boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
 * @return {Query} this
 * @see DeleteResult https://mongodb.github.io/node-mongodb-native/7.0/interfaces/DeleteResult.html
 * @see deleteMany https://mongodb.github.io/node-mongodb-native/7.0/classes/Collection.html#deleteMany
 * @api public
 */

Query.prototype.deleteMany = function(filter, options) {
  if (typeof filter === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') {
    throw new MongooseError('Query.prototype.deleteMany() no longer accepts a callback');
  }
  this.setOptions(options);
  this.op = 'deleteMany';

  if (canMerge(filter)) {
    this.merge(filter);

    prepareDiscriminatorCriteria(this);
  } else if (filter != null) {
    this.error(new ObjectParameterError(filter, 'filter', 'deleteMany'));
  }

  return this;
};

/**
 * Execute a `deleteMany()` query
 *

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Use await: const result = await Model.deleteMany({ stale: true })
  2. Read result.deletedCount for the number removed
  3. Sweep for deleteMany( calls with callbacks during the migration

Example fix

// before
Model.deleteMany({ stale: true }, (err, res) => {
  if (err) return next(err);
  res.json({ removed: res.deletedCount });
});

// after
try {
  const result = await Model.deleteMany({ stale: true });
  res.json({ removed: result.deletedCount });
} catch (err) {
  next(err);
}
Defensive patterns

Strategy: validation

Validate before calling

function deleteManySafe(model, filter, options) {
  if ([filter, options].some(v => typeof v === 'function')) {
    throw new Error('deleteMany() is promise-only in Mongoose 7+');
  }
  return model.deleteMany(filter, options);
}

Type guard

const isLegacyCallback = (v) => typeof v === 'function';

Try / catch

try {
  const result = await Model.deleteMany({ stale: true });
} catch (err) {
  if (err?.message?.includes('no longer accepts a callback')) {
    // fix the deleteMany call site that still passes a callback
  }
  throw err;
}

Prevention

When it happens

Trigger: Model.deleteMany({ stale: true }, (err) => {...}); .deleteMany(cb); a function in the options slot.

Common situations: Scheduled cleanup jobs and TTL sweepers written for Mongoose 6; upgrades to mongoose 7/8; batch-admin endpoints.

Related errors


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