{"record":{"id":"6b4c96c9bd9994e4","repo":"Automattic/mongoose","slug":"document-prototype-populate-no-longer-accepts-a","errorCode":null,"errorMessage":"Document.prototype.populate() no longer accepts a callback","messagePattern":"Document\\.prototype\\.populate\\(\\) no longer accepts a callback","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/document.js","lineNumber":4886,"sourceCode":" * @param {Function} [options.transform=null] Function that Mongoose will call on every populated document that allows you to transform the populated document.\n * @param {object} [options.options=null] Additional options like `limit` and `lean`.\n * @param {boolean} [options.forceRepopulate=true] Set to `false` to prevent Mongoose from repopulating paths that are already populated\n * @param {boolean} [options.ordered=false] Set to `true` to execute any populate queries one at a time, as opposed to in parallel. We recommend setting this option to `true` if using transactions, especially if also populating multiple paths or paths with multiple models. MongoDB server does **not** support multiple operations in parallel on a single transaction.\n * @param {Function} [callback] Callback\n * @see population https://mongoosejs.com/docs/populate.html\n * @see Query#select https://mongoosejs.com/docs/api/query.html#Query.prototype.select()\n * @see Model.populate https://mongoosejs.com/docs/api/model.html#Model.populate()\n * @memberOf Document\n * @instance\n * @return {Promise|null} Returns a Promise if no `callback` is given.\n * @api public\n */\n\nDocument.prototype.populate = async function populate() {\n  const pop = {};\n  const args = [...arguments];\n  if (typeof args[args.length - 1] === 'function') {\n    throw new MongooseError('Document.prototype.populate() no longer accepts a callback');\n  }\n\n  if (args.length !== 0) {\n    // use hash to remove duplicate paths\n    const res = utils.populate.apply(null, args);\n    for (const populateOptions of res) {\n      pop[populateOptions.path] = populateOptions;\n    }\n  }\n\n  const paths = utils.object.vals(pop);\n\n  let topLevelModel = this.constructor;\n  if (this.$__isNested) {\n    topLevelModel = this.$__[scopeSymbol].constructor;\n    const nestedPath = this.$__.nestedPath;\n    paths.forEach(function(populateOptions) {\n      populateOptions.path = nestedPath + '.' + populateOptions.path;","sourceCodeStart":4868,"sourceCodeEnd":4904,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/document.js#L4868-L4904","documentation":"Mongoose 7 removed callback support from all APIs: Document.prototype.populate() is now async and returns a Promise. If the last argument passed is a function, Mongoose treats it as a legacy callback and throws immediately instead of silently never calling it. The guard exists to break old callback-style code loudly during the Mongoose 6 to 7+ migration.","triggerScenarios":"Calling doc.populate('path', (err, d) => {...}), doc.populate({ path: 'path' }, cb), or doc.populate(cb) with no paths - any invocation where the last argument is a function triggers the throw inside the async populate() wrapper.","commonSituations":"Upgrading mongoose from ^6 to ^7/^8 with legacy callback code; snippets copied from pre-2022 tutorials or Stack Overflow answers; wrapper layers that append a callback for backward API compatibility.","solutions":["Remove the callback and await the call: const doc = await doc.populate('path').","Use promise chaining: doc.populate('path').then(d => ...).catch(handleError).","If a callback interface must be kept, wrap it yourself: doc.populate('path').then(d => cb(null, d), cb).","Before upgrading major versions, grep for populate( calls whose last argument is a function or arrow, and migrate them all at once using the mongoose migration guide."],"exampleFix":"// before\ndoc.populate('author', (err, d) => { if (err) throw err; console.log(d.author.name); });\n\n// after\nconst d = await doc.populate('author');\nconsole.log(d.author.name);","handlingStrategy":"validation","validationCode":"// reject callback-style populate before calling mongoose\nfunction safePopulate(doc, ...args) {\n  if (typeof args[args.length - 1] === 'function') {\n    throw new TypeError('populate() is promise-only; remove the callback argument');\n  }\n  return doc.populate(...args);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await doc.populate('author');\n} catch (err) {\n  if (err instanceof mongoose.MongooseError && err.message.includes('no longer accepts a callback')) {\n    // a callback leaked into the call: remove it at the call site and retry promise-style\n    return doc.populate('author');\n  }\n  throw err;\n}","preventionTips":["Standardize on async/await for all mongoose calls; add a lint rule or codemod flagging callback-style mongoose usage before major upgrades.","When upgrading mongoose majors, run the official migration guide checklist and grep for ', cb)' and ', function' near mongoose calls.","Cover populate flows with integration tests so signature regressions fail in CI, not production."],"tags":["mongoose","populate","callback-removed","async-await","migration"],"backgroundTag":"callback-api-removed","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}