{"record":{"id":"25a23e1cb6415e9e","repo":"Automattic/mongoose","slug":"cannot-mix-array-and-object-updates-current-p-25a23e","errorCode":null,"errorMessage":"Cannot mix array and object updates (current: ${_previewUpdate(this[queryUpdateSymbol])}, incoming: ${_previewUpdate(update)})","messagePattern":"Cannot mix array and object updates \\(current: (.+?), incoming: (.+?)\\)","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/query.js","lineNumber":4140,"sourceCode":"  if (update == null || (typeof update === 'object' && utils.hasOwnKeys(update) === false)) {\n    return;\n  }\n\n  if (update instanceof Query) {\n    if (Array.isArray(this[queryUpdateSymbol])) {\n      throw new MongooseError(`Cannot mix array and object updates (current: ${_previewUpdate(this[queryUpdateSymbol])}, incoming: ${_previewUpdate(update[queryUpdateSymbol])})`);\n    }\n    if (update[queryUpdateSymbol]) {\n      utils.mergeClone(this[queryUpdateSymbol], update[queryUpdateSymbol]);\n    }\n  } else if (Array.isArray(update)) {\n    if (!Array.isArray(this[queryUpdateSymbol])) {\n      // `_update` may be empty object by default, like in `doc.updateOne()`\n      // because we create the query first, then run hooks, then apply the update.\n      if (this[queryUpdateSymbol] == null || utils.isEmptyObject(this[queryUpdateSymbol])) {\n        this[queryUpdateSymbol] = [];\n      } else {\n        throw new MongooseError(`Cannot mix array and object updates (current: ${_previewUpdate(this[queryUpdateSymbol])}, incoming: ${_previewUpdate(update)})`);\n      }\n    }\n    this[queryUpdateSymbol] = this[queryUpdateSymbol].concat(update);\n  } else {\n    if (Array.isArray(this[queryUpdateSymbol])) {\n      throw new MongooseError(`Cannot mix array and object updates (current: ${_previewUpdate(this[queryUpdateSymbol])}, incoming: ${_previewUpdate(update)})`);\n    }\n    utils.mergeClone(this[queryUpdateSymbol], update);\n  }\n};\n\nfunction _previewUpdate(update) {\n  const preview = util.inspect(update, {\n    depth: 2,\n    maxArrayLength: 5,\n    breakLength: 80,\n    compact: true\n  });","sourceCodeStart":4122,"sourceCodeEnd":4158,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/query.js#L4122-L4158","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst q = Model.find().updateOne({}, { $set: { a: 1 } });\nq.updateOne({}, [{ $set: { b: 2 } }], { updatePipeline: true }); // throws\n\n// after\nawait Model.updateOne({}, { $set: { a: 1 } });\nawait Model.updateOne({}, [{ $set: { b: 2 } }], { updatePipeline: true });","handlingStrategy":"validation","validationCode":"function chainUpdate(query, update, options = {}) {\n  const current = query.getUpdate();\n  if (Array.isArray(update) && current != null && !Array.isArray(current) && Object.keys(current).length > 0) {\n    throw new Error('Query already has an object update; run the pipeline update separately');\n  }\n  return query.updateOne({}, update, options);","typeGuard":"const canMergeInto = (current, incoming) => Array.isArray(current) === Array.isArray(incoming) || current == null || Object.keys(current).length === 0;","tryCatchPattern":"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; }","preventionTips":["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."],"tags":["mongoose","query","update-pipeline","invalid-argument"],"backgroundTag":"mongoose-mixed-update-types","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}