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$setnames_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
- Saving a whole object back with
$set. Code loads a document, changes it, and sends{ $set: doc },_idincluded, and somewhere along the way the_idbecame a string: afterJSON.stringify, a REST payload or a form. - A replacement built from request data that carries its own
_id, or a new one generated by the client. - An upsert whose filter and update disagree: the filter has
_id: 5and the update sets_id: 6. - 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.