{"record":{"id":"5749ec4428bb193a","repo":"Automattic/mongoose","slug":"aggregate-pipeline-for-unionwith-cannot-include","errorCode":null,"errorMessage":"Aggregate pipeline for $unionWith cannot include `$out` or `$merge` stages","messagePattern":"Aggregate pipeline for \\$unionWith cannot include `\\$out` or `\\$merge` stages","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"warning","filePath":"lib/aggregate.js","lineNumber":1047,"sourceCode":"};\n\n/**\n * Returns the current pipeline as a `$unionWith`-safe pipeline.\n * Throws if this pipeline contains `$out` or `$merge`.\n *\n * #### Example:\n *\n *     const base = MyModel.aggregate().match({ test: 1 });\n *     base.pipelineForUnionWith(); // [{ $match: { test: 1 } }]\n *\n * @return {PipelineStage[]} The current pipeline with `$unionWith` stage restrictions\n * @api public\n */\n\nAggregate.prototype.pipelineForUnionWith = function pipelineForUnionWith() {\n  for (const stage of this._pipeline) {\n    if (stage?.$out != null || stage?.$merge != null) {\n      throw new MongooseError('Aggregate pipeline for $unionWith cannot include `$out` or `$merge` stages');\n    }\n  }\n\n  return this._pipeline;\n};\n\n/**\n * Executes the aggregate pipeline on the currently bound Model.\n *\n * #### Example:\n *     const result = await aggregate.exec();\n *\n * @return {Promise}\n * @api public\n */\n\nAggregate.prototype.exec = async function exec() {\n  if (!this._model && !this._connection) {","sourceCodeStart":1029,"sourceCodeEnd":1065,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/aggregate.js#L1029-L1065","documentation":"The { returnOriginal: true|false } option on findOneAndUpdate()/findOneAndReplace() is the older sibling of { new }: returnOriginal:true returned the pre-update document, false the post-update one. convertNewToReturnDocument() warns and maps returnOriginal:true -> returnDocument:'before', false -> 'after', then removes the key. Internally Mongoose also re-derives returnOriginal for old driver versions, but user code should speak returnDocument only.","triggerScenarios":"Model.findOneAndUpdate(filter, update, { returnOriginal: false }); configs or helpers that forwarded the mongoose.set('returnOriginal') value down into per-query options; findOneAndReplace calls copied from Mongoose 5-era code.","commonSituations":"Same migration wave as the removed global option — old query helpers, repository-layer wrappers, and DAOs that accept an options object and pass it through; mixed codebases where some paths used new and others returnOriginal.","solutions":["Replace with returnDocument: { returnOriginal: false } -> { returnDocument: 'after' }; { returnOriginal: true } -> { returnDocument: 'before' }.","Update shared query wrappers/DAOs so callers cannot pass returnOriginal anymore.","Grep for 'returnOriginal' repo-wide to catch both the global set() form and per-query options in one pass."],"exampleFix":"// before\nconst doc = await Model.findOneAndUpdate(filter, update, { returnOriginal: false });\n\n// after\nconst doc = await Model.findOneAndUpdate(filter, update, { returnDocument: 'after' });","handlingStrategy":"validation","validationCode":"function normalizeUpdateOptions(opts = {}) {\n  const { new: _n, returnOriginal, ...rest } = opts;\n  if (returnOriginal !== undefined) rest.returnDocument = returnOriginal ? 'before' : 'after';\n  return rest;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use returnDocument exclusively in repository/DAO layers.","Sanitize options passed through from older callers with a normalize step.","Grep for 'returnOriginal' (both global set and per-query) during Mongoose upgrades."],"tags":["mongoose","deprecation","findoneandupdate","query-options","upgrade"],"backgroundTag":"deprecated-api-call","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}