Automattic/mongoose · error · DivergentArrayError
For your own good, using `document.save()` to update an arra
Error message
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:
${paths.join('\n ')}
Use Model.updateOne() to update these arrays instead. See https://mongoosejs.com/docs/faq.html#divergent-array-error for more information. What it means
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).
Source
Thrown at lib/document.js:5397
const val = this.$__.primitiveAtomics[data.path];
const op = firstKey(val);
operand(this, where, delta, data, val[op], op);
} else {
value = clone(value, {
depopulate: true,
transform: false,
virtuals: false,
getters: false,
omitUndefined: true,
_isNested: true
});
operand(this, where, delta, data, value);
}
}
}
if (divergent.length) {
throw new DivergentArrayError(divergent);
}
if (this.$__.version) {
this.$__version(where, delta);
}
if (utils.hasOwnKeys(delta) === false) {
return [where, null, unsavedDirty];
}
return [where, delta, unsavedDirty];
};
/**
* Determine if array was populated with some form of filter and is now
* being updated in a manner which could overwrite data unintentionally.
*
* @see https://github.com/Automattic/mongoose/issues/1334View on GitHub (pinned to 49cdab0136)
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.
Example fix
// before
const doc = await Post.findOne({ _id: id }).populate({ path: 'comments', options: { limit: 5 } });
doc.comments.push(newComment);
await doc.save(); // DivergentArrayError
// after
await Post.updateOne({ _id: id }, { $push: { comments: newComment } }); Defensive patterns
Strategy: fallback
Validate before calling
// detect paths save() would refuse before mutating
function divergentPaths(doc) {
return doc.modifiedPaths().filter(path => {
const opts = doc.$populated(path);
if (opts == null) return false;
return opts.options?.skip != null || opts.options?.limit != null ||
opts.match != null || opts.select?._id === 0;
});
}
if (divergentPaths(doc).length > 0) {
// route the mutation through updateOne() instead of save()
await Model.updateOne({ _id: doc._id }, { $push: { comments: newComment } });
} Try / catch
try {
await doc.save();
} catch (err) {
if (err instanceof mongoose.Error.DivergentArrayError) {
// fall back to an atomic update that does not rewrite the whole array
await Model.updateOne({ _id: doc._id }, { $push: { comments: newComment } });
} else {
throw err;
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Paginating populated subdocument arrays; using $slice projections for performance; splice/pull/push on partially loaded arrays inside request handlers followed by save().
Related errors
- Invalid addFields() argument. Must be an object
- Aggregate.prototype.exec() no longer accepts a callback
- `refPath` must be a string or a function that returns a stri
- Document.prototype.populate() no longer accepts a callback
- Expected path "${path}" to be populated
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/e242aa6336fc5e00.
Report an issue: GitHub.