{"record":{"id":"e242aa6336fc5e00","repo":"Automattic/mongoose","slug":"for-your-own-good-using-document-save-to-upda","errorCode":null,"errorMessage":"For your own good, using `document.save()` to update an array which was selected using an $elemMatch or $slice projection OR populated using skip, limit, query conditions, or exclusion of the _id field when the operation results in a $pop or $set of the entire array is not supported. The following path(s) would have been modified unsafely:\n  ${paths.join('\\n  ')}\nUse Model.updateOne() to update these arrays instead. See https://mongoosejs.com/docs/faq.html#divergent-array-error for more information.","messagePattern":"For your own good, using `document\\.save\\(\\)` to update an array which was selected using an \\$elemMatch or \\$slice projection OR populated using skip, limit, query conditions, or exclusion of the _id field when the operation results in a \\$pop or \\$set of the entire array is not supported\\. The following path\\(s\\) would have been modified unsafely:\n  \\$\\{paths\\.join\\('\\\\n  '\\)\\}\nUse Model\\.updateOne\\(\\) to update these arrays instead\\. See https://mongoosejs\\.com/docs/faq\\.html#divergent-array-error for more information\\.","errorType":"exception","errorClass":"DivergentArrayError","httpStatus":null,"severity":"error","filePath":"lib/document.js","lineNumber":5397,"sourceCode":"        const val = this.$__.primitiveAtomics[data.path];\n        const op = firstKey(val);\n        operand(this, where, delta, data, val[op], op);\n      } else {\n        value = clone(value, {\n          depopulate: true,\n          transform: false,\n          virtuals: false,\n          getters: false,\n          omitUndefined: true,\n          _isNested: true\n        });\n        operand(this, where, delta, data, value);\n      }\n    }\n  }\n\n  if (divergent.length) {\n    throw new DivergentArrayError(divergent);\n  }\n\n  if (this.$__.version) {\n    this.$__version(where, delta);\n  }\n\n  if (utils.hasOwnKeys(delta) === false) {\n    return [where, null, unsavedDirty];\n  }\n\n  return [where, delta, unsavedDirty];\n};\n\n/**\n * Determine if array was populated with some form of filter and is now\n * being updated in a manner which could overwrite data unintentionally.\n *\n * @see https://github.com/Automattic/mongoose/issues/1334","sourceCodeStart":5379,"sourceCodeEnd":5415,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/document.js#L5379-L5415","documentation":"Mongoose throws DivergentArrayError from $__delta when document.save() would $set or $pop an entire array whose server-side contents may differ from what was loaded. An array is divergent when it was fetched with an $elemMatch or $slice projection, or populated with skip, limit, match conditions, or _id exclusion - in those cases Mongoose knows it does not hold the full array, and writing the whole array back could clobber unseen documents. This is a deliberate data-safety guard (see the mongoose FAQ divergent-arrays entry linked in the message).","triggerScenarios":"Model.findOne().populate({ path: 'comments', options: { skip: 10, limit: 5 } }) then doc.comments.push(...) and doc.save(); a query using .select({ items: { $slice: 3 } }) or an $elemMatch projection, then mutating or removing items and saving - any save whose delta computes a $set/$pop over such an array.","commonSituations":"Paginating populated subdocument arrays; using $slice projections for performance; splice/pull/push on partially loaded arrays inside request handlers followed by save().","solutions":["Replace the save-based mutation with a direct atomic update: Model.updateOne({ _id: doc._id }, { $push: { comments: newComment } }) (or $pull, or $set on an explicit array index).","Re-fetch or re-populate the array without skip/limit/match/_id-exclusion before mutating and saving.","Avoid $elemMatch/$slice projections on array paths you intend to modify via save().","For fast-growing arrays, move them to their own collection and use real queries instead of populate + save."],"exampleFix":"// before\nconst doc = await Post.findOne({ _id: id }).populate({ path: 'comments', options: { limit: 5 } });\ndoc.comments.push(newComment);\nawait doc.save(); // DivergentArrayError\n\n// after\nawait Post.updateOne({ _id: id }, { $push: { comments: newComment } });","handlingStrategy":"fallback","validationCode":"// detect paths save() would refuse before mutating\nfunction divergentPaths(doc) {\n  return doc.modifiedPaths().filter(path => {\n    const opts = doc.$populated(path);\n    if (opts == null) return false;\n    return opts.options?.skip != null || opts.options?.limit != null ||\n      opts.match != null || opts.select?._id === 0;\n  });\n}\nif (divergentPaths(doc).length > 0) {\n  // route the mutation through updateOne() instead of save()\n  await Model.updateOne({ _id: doc._id }, { $push: { comments: newComment } });\n}","typeGuard":null,"tryCatchPattern":"try {\n  await doc.save();\n} catch (err) {\n  if (err instanceof mongoose.Error.DivergentArrayError) {\n    // fall back to an atomic update that does not rewrite the whole array\n    await Model.updateOne({ _id: doc._id }, { $push: { comments: newComment } });\n  } else {\n    throw err;\n  }\n}","preventionTips":["Treat save() as a whole-document write; use updateOne() with $push/$pull for arrays that are ever partially loaded.","Never combine populate with skip/limit/match on paths your code mutates.","Route array mutations through a repository layer that always uses atomic updates by convention."],"tags":["mongoose","divergent-array","populate","projection","save","data-safety"],"backgroundTag":"divergent-array-error","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}