Automattic/mongoose · warning · MongooseError

Aggregate.prototype.explain() no longer accepts a callback

Error message

Aggregate.prototype.explain() no longer accepts a callback

What it means

mongoose.set('returnOriginal', true|false) was the pre-6 way to control whether findOneAndUpdate returns the document before or after the update. It was replaced by returnDocument ('before'|'after'), and mongoose.set() now warns on every use. Setting is still applied for backwards compat, but Mongoose also hard-errors if you set returnOriginal after returnDocument (or vice versa) — the two options are mutually exclusive to prevent ambiguity.

Source

Thrown at lib/aggregate.js:799

  }

  return this.append({ $redact: expression });
};

/**
 * Execute the aggregation with explain
 *
 * #### Example:
 *
 *     Model.aggregate(..).explain()
 *
 * @param {'queryPlanner'|'executionStats'|'allPlansExecution'} [verbosity]
 * @return {Promise}
 */

Aggregate.prototype.explain = async function explain(verbosity) {
  if (typeof verbosity === 'function' || typeof arguments[1] === 'function') {
    throw new MongooseError('Aggregate.prototype.explain() no longer accepts a callback');
  }
  const model = this._model;

  if (!this._pipeline.length) {
    throw new MongooseError('Aggregate has empty pipeline');
  }

  prepareDiscriminatorPipeline(this._pipeline, this._model.schema);

  const preFilter = buildMiddlewareFilter(this.options, 'pre');
  const postFilter = buildMiddlewareFilter(this.options, 'post');

  // Remove middleware option before passing to MongoDB
  const options = this.options != null ? { ...this.options } : {};
  delete options.middleware;

  try {
    await model.hooks.execPre('aggregate', this, [], { filter: preFilter });

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Replace with the new option: mongoose.set('returnOriginal', false) -> mongoose.set('returnDocument', 'after'); true -> 'before'.
  2. Delete any lingering returnOriginal line once returnDocument is set — mixing them throws immediately.
  3. Prefer per-query options ({ returnDocument: 'after' }) over global config for clearer code.
  4. Grep the codebase for 'returnOriginal' after upgrading to catch config files, not just call sites.

Example fix

// before
mongoose.set('returnOriginal', false);

// after
mongoose.set('returnDocument', 'after');
Defensive patterns

Strategy: validation

Validate before calling

// central config module
function setReturnDocument(mode) {
  if (mode !== 'before' && mode !== 'after') throw new Error("mode must be 'before' or 'after'");
  mongoose.set('returnDocument', mode);
}

Prevention

When it happens

Trigger: mongoose.set('returnOriginal', false) at app bootstrap; config files carried from Mongoose 4/5; setting both 'returnDocument' and 'returnOriginal' (which throws 'Cannot set ... when ... is already set' rather than warning).

Common situations: Apps upgraded across major Mongoose versions where the config line survived; shared config modules used by services on different Mongoose majors; tutorials copied from pre-2020 docs; CI failures after adding returnDocument while an old set('returnOriginal') still exists.

Related errors


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