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/1334

View on GitHub (pinned to 49cdab0136)

Solutions

  1. 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).
  2. Re-fetch or re-populate the array without skip/limit/match/_id-exclusion before mutating and saving.
  3. Avoid $elemMatch/$slice projections on array paths you intend to modify via save().
  4. 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

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


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