Download

Performing an update on the path '_id' would modify the immutable field '_id'

A document’s _id can never change, and your update or replacement would change it, often only its type (a string instead of an ObjectId). Remove _id from the update; to really change an id, insert a copy with the new one and delete the old document.

MongoDB error 66· Tested on MongoDB 8.0.32 (mongosh 2.12.0)· Updated 11 October 2026

Performing an update on the path '_id' would modify the immutable field '_id'

What it means

Every MongoDB document has an _id, and once the document exists its _id is fixed. An update that would give it a different value, or the same value with a different type, is refused with code 66, ImmutableField. The rest of the update isn’t applied either.

There are two wordings, depending on how the update is written:

  • Performing an update on the path '_id' would modify the immutable field '_id': an operator such as $set names _id.
  • After applying the update, the (immutable) field '_id' was found to have been altered to _id: …: a replacement document (replaceOne, findOneAndReplace) carries a different _id. The message shows the value it found.

Setting _id to exactly the value it already has, same type included, is allowed.

Common causes

  1. Saving a whole object back with $set. Code loads a document, changes it, and sends { $set: doc }, _id included, and somewhere along the way the _id became a string: after JSON.stringify, a REST payload or a form.
  2. A replacement built from request data that carries its own _id, or a new one generated by the client.
  3. An upsert whose filter and update disagree: the filter has _id: 5 and the update sets _id: 6.
  4. Trying to rename an id on purpose, for example to change a slug or migrate string ids to ObjectIds.

How to fix it

Leave _id out of the update

Take _id off the object before you build the update:

const { _id, ...changes } = doc
db.products.updateOne({ _id: ObjectId(_id) }, { $set: changes })

The filter finds the document by _id; the update doesn’t need it.

Replace without _id

replaceOne keeps the existing _id when the replacement has none:

const { _id, ...rest } = doc
db.products.replaceOne({ sku: "A-1" }, rest)

Keep the type right

If the stored _id is an ObjectId and your code holds the hex string, convert it before it goes anywhere near an update (new ObjectId(id)), or don’t send it at all. A string _id never equals an ObjectId one, even with the same hex digits.

To change an id, copy and delete

An _id can’t be edited. Insert the document again with the new _id, then delete the old one; in a transaction if other writes could see the gap:

const old = db.products.findOne({ _id: "lamp-old" })
db.products.insertOne({ ...old, _id: "lamp" })
db.products.deleteOne({ _id: "lamp-old" })

Update anything that refers to the old id at the same time.

Reproduce it

MongoDB 8.0.32, mongosh 2.12.0, a document whose _id is ObjectId('65f1c2e9a1b2c3d4e5f6a7b8'). Setting _id to the same hex digits as a string:

db.p.updateOne(
  { _id: ObjectId("65f1c2e9a1b2c3d4e5f6a7b8") },
  { $set: { _id: "65f1c2e9a1b2c3d4e5f6a7b8", name: "Lamp 2" } }
)
MongoServerError: Performing an update on the path '_id' would modify the immutable field '_id'

The error’s code was 66, and name wasn’t changed. The same $set with ObjectId("65f1c2e9a1b2c3d4e5f6a7b8") as _id matched and modified the document. A replacement carrying the string id:

db.p.replaceOne({ sku: "A-1" }, { _id: "65f1c2e9a1b2c3d4e5f6a7b8", sku: "A-1", qty: 6 })
MongoServerError: After applying the update, the (immutable) field '_id' was found to have been altered to _id: "65f1c2e9a1b2c3d4e5f6a7b8"

The same replacement without _id worked and kept the ObjectId. An upsert with _id: 5 in the filter and $set: { _id: 6 } gave the first message again.

In Inlet

Inlet shows each document in the inspector, so you can see whether its _id is an ObjectId or a string. Its editor updates only the fields you edited and keeps each value’s type, and queries use mongosh syntax, so db.products.find({ _id: { $type: "string" } }) lists the documents whose ids are strings.

Inlet: a database client for the Mac

One native app for PostgreSQL, MySQL, SQL Server, SQLite, MongoDB and Redis. It explains errors where they happen, holds your edits until you save them, and keeps production read-only until you say so.

Version 0.1.0 · macOS 26 Tahoe or later · Apple silicon and Intel