Automattic/mongoose · error · MongooseError

Model.countDocuments() no longer accepts a callback

Error message

Model.countDocuments() no longer accepts a callback

What it means

Model.countDocuments(conditions, options) runs a count with schema-based casting of the filter and returns a Query resolving to a number. Mongoose 7 removed callbacks: a function in any of the first three argument slots throws synchronously.

Source

Thrown at lib/model.js:2267

 *
 * The `countDocuments()` function is similar to `count()`, but there are a
 * [few operators that `countDocuments()` does not support](https://mongodb.github.io/node-mongodb-native/7.0/classes/Collection.html#countDocuments).
 * Below are the operators that `count()` supports but `countDocuments()` does not,
 * and the suggested replacement:
 *
 * - `$where`: [`$expr`](https://www.mongodb.com/docs/manual/reference/operator/query/expr/)
 * - `$near`: [`$geoWithin`](https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/) with [`$center`](https://www.mongodb.com/docs/manual/reference/operator/query/center/#op._S_center)
 * - `$nearSphere`: [`$geoWithin`](https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/) with [`$centerSphere`](https://www.mongodb.com/docs/manual/reference/operator/query/centerSphere/#op._S_centerSphere)
 *
 * @param {object} filter
 * @return {Query}
 * @api public
 */

Model.countDocuments = function countDocuments(conditions, options) {
  _checkContext(this, 'countDocuments');
  if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
    throw new MongooseError('Model.countDocuments() no longer accepts a callback');
  }

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

  return mq.countDocuments(conditions);
};


/**
 * Creates a Query for a `distinct` operation.
 *
 * #### Example:
 *
 *     const query = Link.distinct('url');
 *     query.exec();

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Await it: `const n = await User.countDocuments({ active: true });`
  2. For whole-collection estimates use `estimatedDocumentCount()` (also promise-only)
  3. Include `countDocuments\(` in the migration grep list

Example fix

// before
User.countDocuments({ active: true }, (err, count) => { ... });

// after
const count = await User.countDocuments({ active: true });
Defensive patterns

Strategy: validation

Validate before calling

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

Prevention

When it happens

Trigger: `User.countDocuments({ active: true }, cb)`; `User.countDocuments(cb)` for total counts; pagination helpers from the callback era.

Common situations: Pagination/ dashboard count queries written pre-Mongoose-7; upgrade sweeps that missed count calls because they are less frequent than find/findOne.

Related errors


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