Automattic/mongoose · warning · MongooseError

Aggregate has empty pipeline

Error message

Aggregate has empty pipeline

What it means

findOneAndUpdate() and findOneAndReplace() historically accepted { new: true } to get the updated document back. Mongoose normalized this through convertNewToReturnDocument(): it warns, maps new:true -> returnDocument:'after' and new:false -> 'before', then deletes the new key. Behavior is unchanged; the warning pushes you to the canonical returnDocument option.

Source

Thrown at lib/aggregate.js:804

/**
 * 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 });
  } catch (error) {
    return await model.hooks.execPost('aggregate', this, [null], { error, filter: postFilter });
  }

  const cursor = await model.collection.aggregate(this._pipeline, options);

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Rename the option: { new: true } -> { returnDocument: 'after' }; { new: false } or omission stays 'before' by default.
  2. Apply a codemod across the repo: grep for "new: true" near findOneAndUpdate/findOneAndReplace/findByIdAndUpdate and replace.
  3. Do not mix styles per query — standardize on returnDocument to keep greppability.

Example fix

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

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

Strategy: validation

Validate before calling

const AFTER = { returnDocument: 'after' };
// one shared constant instead of inline { new: true }
const doc = await Model.findOneAndUpdate(filter, update, { ...AFTER, runValidators: true });

Prevention

When it happens

Trigger: await Model.findOneAndUpdate(filter, update, { new: true }); the object form findByIdAndUpdate(id, update, { new: true, runValidators: true }); any codebase patterned on pre-6 docs/tutorials that used new: true.

Common situations: Extremely common in legacy code — { new: true } was the idiomatic form for years; Stack Overflow answers copied verbatim; gradual migrations where some queries already use returnDocument; test snapshots polluted by the warning text.

Related errors


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