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
- Replace with the new option: mongoose.set('returnOriginal', false) -> mongoose.set('returnDocument', 'after'); true -> 'before'.
- Delete any lingering returnOriginal line once returnDocument is set — mixing them throws immediately.
- Prefer per-query options ({ returnDocument: 'after' }) over global config for clearer code.
- 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
- Grep for 'returnOriginal' whenever upgrading Mongoose majors and migrate to returnDocument.
- Never set both returnOriginal and returnDocument — Mongoose throws on the combination.
- Keep global query config in one bootstrap module so stale options are easy to spot.
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
- Aggregate has empty pipeline
- Aggregate pipeline for $unionWith cannot include `$out` or `
- Aggregate `near()` argument must have a `near` property
- Aggregate `near()` argument has invalid coordinates, got "${
- The `uri` parameter to `openUri()` must be a string, got "${
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/c227c7abef230bfa.
Report an issue: GitHub.