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
- Use one update format per query: rewrite the earlier object update as a pipeline stage, or the later array as an object.
- Start a new query (Model.updateOne(...)) for the second update instead of merging into the existing one.
- 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
- Create a fresh Model.updateOne() per statement instead of chaining onto one Query.
- Make plugin-added update merges aware of pipeline format.
- Cover chained-update helpers with unit tests using both formats.
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
- Cannot mix array and object updates (current: ${_previewUpda
- Invalid addFields() argument. Must be an object
- Invalid select() argument. Must be string or object.
- Options must be an object, got "${options}"
- sort() takes at most 2 arguments
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/25a23e1cb6415e9e.
Report an issue: GitHub.