{"record":{"id":"66dd1203846d44e3","repo":"Automattic/mongoose","slug":"document-deleteone-pre-hooks-cannot-overwrite-argu","errorCode":null,"errorMessage":"Document deleteOne pre hooks cannot overwrite arguments","messagePattern":"Document deleteOne pre hooks cannot overwrite arguments","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/model.js","lineNumber":848,"sourceCode":"  const self = this;\n  const where = this.$__where();\n  const query = self.constructor.deleteOne();\n\n  if (this.$session() != null) {\n    if (!('session' in query.options)) {\n      query.options.session = this.$session();\n    }\n  }\n\n  const preFilter = buildMiddlewareFilter(options, 'pre');\n  const postFilter = buildMiddlewareFilter(options, 'post');\n\n  query.pre(async function queryPreDeleteOne() {\n    const res = await self.constructor._middleware.execPre('deleteOne', self, [self, options], { filter: preFilter });\n    // `self` is passed to pre hooks as argument for backwards compatibility, but that\n    // isn't the actual arguments passed to the wrapped function.\n    if (res[0] !== self || res[1] !== options) {\n      throw new MongooseError('Document deleteOne pre hooks cannot overwrite arguments');\n    }\n    query.deleteOne(where, options);\n    // Apply custom where conditions _after_ document deleteOne middleware for\n    // consistency with save() - sharding plugin needs to set $where\n    if (self.$where != null) {\n      this.where(self.$where);\n    }\n    return res;\n  });\n  query.pre(function callSubdocPreHooks() {\n    return Promise.all(self.$getAllSubdocs().map(subdoc => subdoc.constructor._middleware.execPre('deleteOne', subdoc, [subdoc], { filter: preFilter })));\n  });\n  query.pre(function skipIfAlreadyDeleted() {\n    if (self.$__.isDeleted) {\n      throw new Kareem.skipWrappedFunction();\n    }\n  });\n  query.post(function callSubdocPostHooks() {","sourceCodeStart":830,"sourceCodeEnd":866,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/model.js#L830-L866","documentation":"Document-style deleteOne middleware (pre('deleteOne', { document: true, query: false })) runs inside Mongoose's wrapper with fixed arguments (doc, options). If the pre hook replaces those arguments — classically by calling next() with values, e.g. next(null, otherDoc, otherOptions), or via a plugin that proxies and forwards replaced args — Mongoose throws this MongooseError, because its wrapper must execute with the original document and options.","triggerScenarios":"A schema hook like schema.pre('deleteOne', { document: true, query: false }, function (next) { next(null, this, {}); }) followed by doc.deleteOne(); also plugins (soft-delete, audit trails) that wrap deleteOne middleware and pass replaced arguments through.","commonSituations":"Writing hooks with other libraries' semantics where next() carries results; porting query-middleware patterns that reshape arguments to document middleware; plugin interop layers around deleteOne.","solutions":["Call next() with no arguments (or write async hooks with no return value) in document deleteOne pre hooks","Modify behavior through `this` (the document) instead of replacing arguments","Apply custom conditions via this.$where, or use query-level hooks (pre('deleteOne', { query: true, document: false })) and shape the Query"],"exampleFix":"// before\nschema.pre('deleteOne', { document: true, query: false }, function (next) {\n  next(null, this, { soft: true }); // replaces (doc, options) -> throws\n});\n\n// after\nschema.pre('deleteOne', { document: true, query: false }, async function () {\n  this.deletedAt = new Date(); // mutate the document instead\n});","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await doc.deleteOne();\n} catch (err) {\n  if (err instanceof mongoose.MongooseError && /pre hooks cannot overwrite arguments/.test(err.message)) {\n    // audit registered pre('deleteOne', { document: true }) hooks and plugins:\n    // ensure they call next() with no arguments and mutate `this` instead\n  } else throw err;\n}","preventionTips":["In document deleteOne pre hooks, always call next() with zero arguments (or use async functions without return values)","Change document state via `this`, extra conditions via this.$where, never by replacing hook arguments","When integrating plugins that wrap middleware, verify they pass arguments through unchanged"],"tags":["mongoose","middleware","hooks","deleteone"],"backgroundTag":"middleware-hook-misuse","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}