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

  1. Use await: const roles = await Model.distinct('role')
  2. Or await Model.distinct('role', { active: true }) for a filtered distinct
  3. 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

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


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