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
- Use await: const n = await Model.countDocuments({ status: 'active' })
- For existence checks, prefer Model.exists(filter) or .findOne(filter).lean()
- 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
- Replace countDocuments(filter, cb) totals with awaited calls
- Prefer Model.exists(filter) for existence checks
- Add a repo-wide search for ', cb)' or 'err =>' during major upgrades
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
- Query.prototype.estimatedDocumentCount() no longer accepts a
- Query.prototype.find() no longer accepts a callback
- Query.prototype.findOne() no longer accepts a callback
- Query.prototype.distinct() no longer accepts a callback
- Query.prototype.deleteOne() no longer accepts a callback
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/0f71373be6349ca0.
Report an issue: GitHub.