{"record":{"id":"7aa32a07c4f1f119","repo":"Automattic/mongoose","slug":"query-prototype-findoneandupdate-no-longer-accep","errorCode":null,"errorMessage":"Query.prototype.findOneAndUpdate() no longer accepts a callback","messagePattern":"Query\\.prototype\\.findOneAndUpdate\\(\\) no longer accepts a callback","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/query.js","lineNumber":3489,"sourceCode":" * @param {'before'|'after'} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.\n * @param {boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.\n * @param {boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key\n * @param {boolean} [options.overwriteImmutable=false] Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert). Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.\n * @param {boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)\n * @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html\n * @see findAndModify command https://www.mongodb.com/docs/manual/reference/command/findAndModify/\n * @see ModifyResult https://mongodb.github.io/node-mongodb-native/7.0/interfaces/ModifyResult.html\n * @see findOneAndUpdate https://mongodb.github.io/node-mongodb-native/7.0/classes/Collection.html#findOneAndUpdate\n * @return {Query} this\n * @api public\n */\n\nQuery.prototype.findOneAndUpdate = function(filter, update, options) {\n  if (typeof filter === 'function' ||\n      typeof update === 'function' ||\n      typeof options === 'function' ||\n      typeof arguments[3] === 'function') {\n    throw new MongooseError('Query.prototype.findOneAndUpdate() no longer accepts a callback');\n  }\n\n  this.op = 'findOneAndUpdate';\n  this._validate();\n\n  switch (arguments.length) {\n    case 2:\n      options = undefined;\n      break;\n    case 1:\n      update = filter;\n      filter = options = undefined;\n      break;\n  }\n\n  if (canMerge(filter)) {\n    this.merge(filter);\n  } else if (filter != null) {","sourceCodeStart":3471,"sourceCodeEnd":3507,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/query.js#L3471-L3507","documentation":"Query.prototype.findOneAndUpdate(filter, update, options) throws if any of the three declared parameters or a fourth positional argument is a function. Note the argument-shifting logic: with fewer arguments, the method reinterprets positions (one argument is treated as the update with an empty filter), so a lone callback both trips this guard immediately and would otherwise be misparsed as an update document.","triggerScenarios":"Model.findOneAndUpdate({ _id }, { $set: { name: 'x' } }, { returnDocument: 'after' }, cb); .findOneAndUpdate(filter, cb) with the callback in the update slot; Mongoose 5/6 tutorial snippets combining options and callback.","commonSituations":"Upgrading apps that used Model.findOneAndUpdate(..., { new: true }, cb); the historically most-copied Mongoose snippet; partial migrations leaving one call site behind.","solutions":["Use await: const doc = await Model.findOneAndUpdate(filter, update, { returnDocument: 'after' })","Handle the not-found case: the promise resolves null when nothing matched","Remove every trailing callback from findOneAndUpdate call sites; check the migration guide for option renames (new -> returnDocument)"],"exampleFix":"// before\nModel.findOneAndUpdate({ _id }, { $set: { name: 'x' } }, { new: true }, (err, doc) => {\n  if (err) return next(err);\n  res.json(doc);\n});\n\n// after\ntry {\n  const doc = await Model.findOneAndUpdate(\n    { _id },\n    { $set: { name: 'x' } },\n    { returnDocument: 'after' }\n  );\n  res.json(doc);\n} catch (err) {\n  next(err);\n}","handlingStrategy":"validation","validationCode":"function findOneAndUpdateSafe(model, filter, update, options) {\n  if ([filter, update, options].some(v => typeof v === 'function')) {\n    throw new Error('findOneAndUpdate() is promise-only in Mongoose 7+');\n  }\n  return model.findOneAndUpdate(filter, update, options);\n}","typeGuard":"const isLegacyCallback = (v) => typeof v === 'function';","tryCatchPattern":"try {\n  const doc = await Model.findOneAndUpdate(filter, update, { returnDocument: 'after' });\n} catch (err) {\n  if (err?.message?.includes('no longer accepts a callback')) {\n    // fix the findOneAndUpdate call site that still passes a callback\n  }\n  throw err;\n}","preventionTips":["This is the most commonly copied legacy snippet — audit it first in upgrades","Replace the old { new: true } + callback combo with options + await","Handle the null result for no match instead of relying on callback err"],"tags":["mongoose","callback","promise","migration","breaking-change","findoneandupdate"],"backgroundTag":"legacy-callback-removed","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}