Automattic/mongoose · error · MongooseError
Query.prototype.distinct() no longer accepts a callback
Error message
Query.prototype.distinct() no longer accepts a callback
What it means
Query.prototype.distinct(field, conditions, options) checks all three declared parameters plus arguments[3]; any function triggers this throw. Distinct queries have been promise-only since Mongoose 7, so Model.distinct('role', {}, cb) fails before the command reaches MongoDB.
Source
Thrown at lib/query.js:3079
* distinct(field, conditions, options)
* distinct(field, conditions)
* distinct(field)
* distinct()
*
* @param {string} [field]
* @param {object|Query} [filter]
* @param {object} [options]
* @return {Query} this
* @see distinct https://www.mongodb.com/docs/manual/reference/method/db.collection.distinct/
* @api public
*/
Query.prototype.distinct = function(field, conditions, options) {
if (typeof field === 'function' ||
typeof conditions === 'function' ||
typeof options === 'function' ||
typeof arguments[3] === 'function') {
throw new MongooseError('Query.prototype.distinct() no longer accepts a callback');
}
this.op = 'distinct';
if (canMerge(conditions)) {
this.merge(conditions);
prepareDiscriminatorCriteria(this);
} else if (conditions != null) {
this.error(new ObjectParameterError(conditions, 'filter', 'distinct'));
}
if (field != null) {
this._distinct = field;
}
if (options != null) {
this.setOptions(options);View on GitHub (pinned to 49cdab0136)
Solutions
- Use await: const roles = await Model.distinct('role')
- Or await Model.distinct('role', { active: true }) for a filtered distinct
- Search for distinct( calls containing 'err =>' and migrate them to async/await
Example fix
// before
Model.distinct('role', { active: true }, (err, roles) => {
if (err) return next(err);
res.json(roles);
});
// after
try {
const roles = await Model.distinct('role', { active: true });
res.json(roles);
} catch (err) {
next(err);
} Defensive patterns
Strategy: validation
Validate before calling
function distinctSafe(model, field, conditions, options) {
if ([field, conditions, options].some(v => typeof v === 'function')) {
throw new Error('distinct() is promise-only in Mongoose 7+');
}
return model.distinct(field, conditions, options);
} Type guard
const isLegacyCallback = (v) => typeof v === 'function';
Try / catch
try {
const roles = await Model.distinct('role');
} catch (err) {
if (err?.message?.includes('no longer accepts a callback')) {
// fix the distinct call site that still passes a callback
}
throw err;
} Prevention
- Await distinct queries instead of passing nodebacks
- Keep enumeration endpoints on async handlers
- Check the migration guide table of removed callback signatures
When it happens
Trigger: Model.distinct('role', {}, (err, roles) => {...}); .distinct(cb) with the callback in the field slot; a function passed where conditions is expected.
Common situations: Building dropdown/enumeration endpoints from legacy callback code; mongoose major upgrades; copy-pasted pre-2017 snippets.
Related errors
- Query.prototype.find() no longer accepts a callback
- Query.prototype.findOne() no longer accepts a callback
- Query.prototype.estimatedDocumentCount() no longer accepts a
- Query.prototype.countDocuments() no longer accepts a callbac
- Query.prototype.deleteOne() no longer accepts a callback
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/c51dce1b8c009c9a.
Report an issue: GitHub.