Automattic/mongoose · error · MongooseError

Cannot mix array and object updates (current: ${_previewUpda

Error message

Cannot mix array and object updates (current: ${_previewUpdate(this[queryUpdateSymbol])}, incoming: ${_previewUpdate(update)})

What it means

Thrown by Query.prototype._mergeUpdate when the incoming update is an array (pipeline update) but the query already holds a non-empty plain-object update. The only tolerated transition is from an empty object (as created internally by doc.updateOne()) to an array; anything else mixes the two update formats, which the server cannot accept, so Mongoose throws with a preview of both values.

Source

Thrown at lib/query.js:4140

  if (update == null || (typeof update === 'object' && utils.hasOwnKeys(update) === false)) {
    return;
  }

  if (update instanceof Query) {
    if (Array.isArray(this[queryUpdateSymbol])) {
      throw new MongooseError(`Cannot mix array and object updates (current: ${_previewUpdate(this[queryUpdateSymbol])}, incoming: ${_previewUpdate(update[queryUpdateSymbol])})`);
    }
    if (update[queryUpdateSymbol]) {
      utils.mergeClone(this[queryUpdateSymbol], update[queryUpdateSymbol]);
    }
  } else if (Array.isArray(update)) {
    if (!Array.isArray(this[queryUpdateSymbol])) {
      // `_update` may be empty object by default, like in `doc.updateOne()`
      // because we create the query first, then run hooks, then apply the update.
      if (this[queryUpdateSymbol] == null || utils.isEmptyObject(this[queryUpdateSymbol])) {
        this[queryUpdateSymbol] = [];
      } else {
        throw new MongooseError(`Cannot mix array and object updates (current: ${_previewUpdate(this[queryUpdateSymbol])}, incoming: ${_previewUpdate(update)})`);
      }
    }
    this[queryUpdateSymbol] = this[queryUpdateSymbol].concat(update);
  } else {
    if (Array.isArray(this[queryUpdateSymbol])) {
      throw new MongooseError(`Cannot mix array and object updates (current: ${_previewUpdate(this[queryUpdateSymbol])}, incoming: ${_previewUpdate(update)})`);
    }
    utils.mergeClone(this[queryUpdateSymbol], update);
  }
};

function _previewUpdate(update) {
  const preview = util.inspect(update, {
    depth: 2,
    maxArrayLength: 5,
    breakLength: 80,
    compact: true
  });

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Use one update format per query: rewrite the earlier object update as a pipeline stage, or the later array as an object.
  2. Start a new query (Model.updateOne(...)) for the second update instead of merging into the existing one.
  3. Audit plugins/middleware that call _mergeUpdate indirectly via chained update calls and make them format-aware.

Example fix

// before
const q = Model.find().updateOne({}, { $set: { a: 1 } });
q.updateOne({}, [{ $set: { b: 2 } }], { updatePipeline: true }); // throws

// after
await Model.updateOne({}, { $set: { a: 1 } });
await Model.updateOne({}, [{ $set: { b: 2 } }], { updatePipeline: true });
Defensive patterns

Strategy: validation

Validate before calling

function chainUpdate(query, update, options = {}) {
  const current = query.getUpdate();
  if (Array.isArray(update) && current != null && !Array.isArray(current) && Object.keys(current).length > 0) {
    throw new Error('Query already has an object update; run the pipeline update separately');
  }
  return query.updateOne({}, update, options);

Type guard

const canMergeInto = (current, incoming) => Array.isArray(current) === Array.isArray(incoming) || current == null || Object.keys(current).length === 0;

Try / catch

try { q.updateOne({}, incoming); } catch (err) { if (/Cannot mix array and object updates/.test(err.message)) { await Model.updateOne(q.getFilter(), incoming); return; } throw err; }

Prevention

When it happens

Trigger: `q.updateOne({}, { $set: { a: 1 } }); q.updateOne({}, [{ $set: { b: 2 } }], { updatePipeline: true })` on the same Query instance; calling doc.updateOne() twice on the same document query with different update shapes; query builders that chain .update()-style calls.

Common situations: Middleware or plugins that append extra $set fields to an existing update while the application code switched that update to pipeline form; gradually migrating an endpoint from operator updates to pipeline updates while a helper still merges operator objects.

Related errors


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