Automattic/mongoose · warning · MongooseError

Aggregate pipeline for $unionWith cannot include `$out` or `

Error message

Aggregate pipeline for $unionWith cannot include `$out` or `$merge` stages

What it means

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.

Source

Thrown at lib/aggregate.js:1047

};

/**
 * Returns the current pipeline as a `$unionWith`-safe pipeline.
 * Throws if this pipeline contains `$out` or `$merge`.
 *
 * #### Example:
 *
 *     const base = MyModel.aggregate().match({ test: 1 });
 *     base.pipelineForUnionWith(); // [{ $match: { test: 1 } }]
 *
 * @return {PipelineStage[]} The current pipeline with `$unionWith` stage restrictions
 * @api public
 */

Aggregate.prototype.pipelineForUnionWith = function pipelineForUnionWith() {
  for (const stage of this._pipeline) {
    if (stage?.$out != null || stage?.$merge != null) {
      throw new MongooseError('Aggregate pipeline for $unionWith cannot include `$out` or `$merge` stages');
    }
  }

  return this._pipeline;
};

/**
 * Executes the aggregate pipeline on the currently bound Model.
 *
 * #### Example:
 *     const result = await aggregate.exec();
 *
 * @return {Promise}
 * @api public
 */

Aggregate.prototype.exec = async function exec() {
  if (!this._model && !this._connection) {

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Replace with returnDocument: { returnOriginal: false } -> { returnDocument: 'after' }; { returnOriginal: true } -> { returnDocument: 'before' }.
  2. Update shared query wrappers/DAOs so callers cannot pass returnOriginal anymore.
  3. Grep for 'returnOriginal' repo-wide to catch both the global set() form and per-query options in one pass.

Example fix

// before
const doc = await Model.findOneAndUpdate(filter, update, { returnOriginal: false });

// after
const doc = await Model.findOneAndUpdate(filter, update, { returnDocument: 'after' });
Defensive patterns

Strategy: validation

Validate before calling

function normalizeUpdateOptions(opts = {}) {
  const { new: _n, returnOriginal, ...rest } = opts;
  if (returnOriginal !== undefined) rest.returnDocument = returnOriginal ? 'before' : 'after';
  return rest;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21). Data as JSON: /api/errors/5749ec4428bb193a. Report an issue: GitHub.