{"record":{"id":"b19aed84ccedacda","repo":"Automattic/mongoose","slug":"model-prototype-save-no-longer-accepts-a-callbac","errorCode":null,"errorMessage":"Model.prototype.save() no longer accepts a callback","messagePattern":"Model\\.prototype\\.save\\(\\) no longer accepts a callback","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/model.js","lineNumber":639,"sourceCode":" * @param {boolean} [options.validateModifiedOnly=false] if `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths.\n * @param {number|string} [options.w] set the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#w-option). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern)\n * @param {boolean} [options.j] set to true for MongoDB to wait until this `save()` has been [journaled before resolving the returned promise](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern)\n * @param {number} [options.wtimeout] sets a [timeout for the write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern).\n * @param {boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://docs.mongodb.com/manual/reference/limits/#mongodb-limit-Restrictions-on-Field-Names)\n * @param {boolean} [options.timestamps=true] if `false` and [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this `save()`.\n * @param {Array} [options.pathsToSave] An array of paths that tell mongoose to only validate and save the paths in `pathsToSave`.\n * @param {boolean|object} [options.middleware=true] set to `false` to skip all user-defined middleware\n * @param {boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks\n * @param {boolean} [options.middleware.post=true] set to `false` to skip only post hooks\n * @throws {DocumentNotFoundError} if this [save updates an existing document](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating).\n * @return {Promise}\n * @api public\n * @see middleware https://mongoosejs.com/docs/middleware.html\n */\n\nModel.prototype.save = async function save(options) {\n  if (typeof options === 'function' || typeof arguments[1] === 'function') {\n    throw new MongooseError('Model.prototype.save() no longer accepts a callback');\n  }\n\n  let parallelSave;\n  this.$op = 'save';\n\n  if (this.$__.saving) {\n    parallelSave = new ParallelSaveError(this);\n  } else {\n    this.$__.saving = true;\n  }\n\n  options = new SaveOptions(options);\n  if (Object.hasOwn(options, 'session')) {\n    this.$session(options.session);\n  }\n  if (this.$__.timestamps != null) {\n    options.timestamps = this.$__.timestamps;\n  }","sourceCodeStart":621,"sourceCodeEnd":657,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/model.js#L621-L657","documentation":"Mongoose 7 removed callback-style APIs: Model.prototype.save() accepts only an options object and returns a Promise. If save() is called with a function as its first or second argument (the legacy (callback) or (options, callback) forms), it throws this MongooseError so the never-invoked callback fails loudly instead of silently.","triggerScenarios":"doc.save(function (err) { ... }) or doc.save({ session }, function (err) { ... }) on Mongoose 7+, typically after upgrading from Mongoose 5/6 without migrating call sites.","commonSituations":"mongoose 6-to-7 (or 5-to-7) upgrades; legacy Express handlers written against callback docs; wrapper libraries that forward user-supplied callbacks; old tutorials and generated code.","solutions":["Replace callbacks with await inside try/catch: `try { const d = await doc.save(); } catch (err) { ... }`","If a boundary must keep a callback API, promisify internally: doc.save(opts).then(r => cb(null, r), cb)","Grep the codebase for `.save(function` and `.save(` with trailing callback args as part of the Mongoose 7 migration"],"exampleFix":"// before\ndoc.save(function (err, saved) {\n  if (err) return next(err);\n  res.json(saved);\n});\n\n// after\ntry {\n  const saved = await doc.save();\n  res.json(saved);\n} catch (err) {\n  next(err);\n}","handlingStrategy":"validation","validationCode":"// Reject legacy callback usage at a wrapper boundary\nfunction assertNoCallback(options, arg1) {\n  if (typeof options === 'function' || typeof arg1 === 'function') {\n    throw new Error('save() is promise-based — await it instead of passing a callback');\n  }\n}","typeGuard":"const isCallback = (x) => typeof x === 'function';","tryCatchPattern":"try {\n  await doc.save({ session });\n} catch (err) {\n  // handle validation / Mongo errors here; callbacks no longer exist\n}","preventionTips":["Migrate all save() call sites to async/await during the Mongoose 7 upgrade","Grep for `.save(function` and `.save(.*, *function` and fix every hit","Keep wrapper layers promise-based; adapt to callbacks only at the outermost legacy edge"],"tags":["mongoose","save","callback","promises","migration"],"backgroundTag":"callback-api-removed","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}