{"record":{"id":"c51dce1b8c009c9a","repo":"Automattic/mongoose","slug":"query-prototype-distinct-no-longer-accepts-a-cal","errorCode":null,"errorMessage":"Query.prototype.distinct() no longer accepts a callback","messagePattern":"Query\\.prototype\\.distinct\\(\\) no longer accepts a callback","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/query.js","lineNumber":3079,"sourceCode":" *     distinct(field, conditions, options)\n *     distinct(field, conditions)\n *     distinct(field)\n *     distinct()\n *\n * @param {string} [field]\n * @param {object|Query} [filter]\n * @param {object} [options]\n * @return {Query} this\n * @see distinct https://www.mongodb.com/docs/manual/reference/method/db.collection.distinct/\n * @api public\n */\n\nQuery.prototype.distinct = function(field, conditions, options) {\n  if (typeof field === 'function' ||\n      typeof conditions === 'function' ||\n      typeof options === 'function' ||\n      typeof arguments[3] === 'function') {\n    throw new MongooseError('Query.prototype.distinct() no longer accepts a callback');\n  }\n\n  this.op = 'distinct';\n\n  if (canMerge(conditions)) {\n    this.merge(conditions);\n\n    prepareDiscriminatorCriteria(this);\n  } else if (conditions != null) {\n    this.error(new ObjectParameterError(conditions, 'filter', 'distinct'));\n  }\n\n  if (field != null) {\n    this._distinct = field;\n  }\n\n  if (options != null) {\n    this.setOptions(options);","sourceCodeStart":3061,"sourceCodeEnd":3097,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/query.js#L3061-L3097","documentation":"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.","triggerScenarios":"Model.distinct('role', {}, (err, roles) => {...}); .distinct(cb) with the callback in the field slot; a function passed where conditions is expected.","commonSituations":"Building dropdown/enumeration endpoints from legacy callback code; mongoose major upgrades; copy-pasted pre-2017 snippets.","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"],"exampleFix":"// before\nModel.distinct('role', { active: true }, (err, roles) => {\n  if (err) return next(err);\n  res.json(roles);\n});\n\n// after\ntry {\n  const roles = await Model.distinct('role', { active: true });\n  res.json(roles);\n} catch (err) {\n  next(err);\n}","handlingStrategy":"validation","validationCode":"function distinctSafe(model, field, conditions, options) {\n  if ([field, conditions, options].some(v => typeof v === 'function')) {\n    throw new Error('distinct() is promise-only in Mongoose 7+');\n  }\n  return model.distinct(field, conditions, options);\n}","typeGuard":"const isLegacyCallback = (v) => typeof v === 'function';","tryCatchPattern":"try {\n  const roles = await Model.distinct('role');\n} catch (err) {\n  if (err?.message?.includes('no longer accepts a callback')) {\n    // fix the distinct call site that still passes a callback\n  }\n  throw err;\n}","preventionTips":["Await distinct queries instead of passing nodebacks","Keep enumeration endpoints on async handlers","Check the migration guide table of removed callback signatures"],"tags":["mongoose","callback","promise","migration","breaking-change","distinct"],"backgroundTag":"legacy-callback-removed","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}