Automattic/mongoose · error · MongooseError

Query.prototype.countDocuments() no longer accepts a callbac

Error message

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

What it means

Query.prototype.countDocuments(conditions, options) throws if conditions, options, or a third positional argument is a function. Since Mongoose 7 the method is promise-only, so the classic tutorial pattern Model.countDocuments({}, cb) fails at query-construction time, before any database call.

Source

Thrown at lib/query.js:3012

 * 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] mongodb selector
 * @param {object} [options]
 * @return {Query} this
 * @see countDocuments https://mongodb.github.io/node-mongodb-native/7.0/classes/Collection.html#countDocuments
 * @api public
 */

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

  this.op = 'countDocuments';

  if (canMerge(conditions)) {
    this.merge(conditions);
  }

  if (options != null) {
    this.setOptions(options);
  }

  return this;
};

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

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Use await: const n = await Model.countDocuments({ status: 'active' })
  2. For existence checks, prefer Model.exists(filter) or .findOne(filter).lean()
  3. During the 6-to-7 upgrade, grep for countDocuments( with a trailing function and remove all callbacks

Example fix

// before
Model.countDocuments({ active: true }, (err, count) => {
  if (err) return next(err);
  res.json({ count });
});

// after
try {
  const count = await Model.countDocuments({ active: true });
  res.json({ count });
} catch (err) {
  next(err);
}
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

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

Try / catch

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

Prevention

When it happens

Trigger: Model.countDocuments({}, (err, n) => {...}); .countDocuments(cb) with the callback in the filter slot; a function passed in the options slot.

Common situations: Legacy pagination totals and existence checks written against Mongoose 6; mongoose major-version upgrades; partial migrations where one count call was missed.

Related errors


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