Skip to content

Updating documents

Updating is the operation people most often get subtly wrong, because the obvious mental model — “find the document and overwrite a field” — is not how MongoDB works by default. An update is two documents: a filter that selects which documents to change, and an update document built from operators that describe the change. The operators are the whole point. They let you bump a number, set a field, remove a field, or push onto an array without touching anything else in the document.

We will keep editing our library members. Every example below shows the document before and after so you can see exactly what each operator did.

$set writes a value into a field. If the field exists it is overwritten; if it does not exist it is created. updateOne applies the change to the first document that matches the filter and stops there. Here we record that Edsger paid off his fines:

db.members.updateOne(
{ name: "Edsger" },
{ $set: { fines: 0 } }
)

Before the update, Edsger owed five:

{ "_id": "65f0b3d4e4b0a1c2e4b0a1d3", "name": "Edsger", "joined": 2022, "fines": 5 }

After it, only the fines field changed:

{ "_id": "65f0b3d4e4b0a1c2e4b0a1d3", "name": "Edsger", "joined": 2022, "fines": 0 }

The acknowledgement tells you how the operation went. matchedCount is how many documents the filter found; modifiedCount is how many actually changed:

{ "acknowledged": true, "matchedCount": 1, "modifiedCount": 1 }

If you run the very same update again, the value is already 0, so matchedCount stays 1 but modifiedCount is 0 — the document matched but nothing needed changing.

$inc adds a number to a field (use a negative number to subtract). It is atomic, which is the right tool for counters and balances. $set doubles as a way to add a field that was never there — here we both charge a late fee and stamp the member with a lastSeen year in one call:

db.members.updateOne(
{ name: "Linus" },
{ $inc: { fines: 3 }, $set: { lastSeen: 2026 } }
)

Before, Linus had two fines and no lastSeen field:

{ "_id": "65f0b3d4e4b0a1c2e4b0a1d1", "name": "Linus", "joined": 2023, "fines": 2 }

After, the count went up by three and a brand-new field appeared:

{
"_id": "65f0b3d4e4b0a1c2e4b0a1d1",
"name": "Linus",
"joined": 2023,
"fines": 5,
"lastSeen": 2026
}

To delete a field entirely — not set it to null, but make it stop existing — use $unset. The value you give it is ignored; only the field name matters. Here we strip the lastSeen field back off Linus:

db.members.updateOne(
{ name: "Linus" },
{ $unset: { lastSeen: "" } }
)

The lastSeen field is gone again, and nothing else moved:

{ "_id": "65f0b3d4e4b0a1c2e4b0a1d1", "name": "Linus", "joined": 2023, "fines": 5 }

Documents often hold arrays, and you rarely want to replace the whole array to add or remove one element. $push appends an element; $pull removes every element matching a value. Imagine each member carries a borrowed array of book titles. First we lend a book to Margaret:

db.members.updateOne(
{ name: "Margaret" },
{ $push: { borrowed: "Compilers" } }
)

If the borrowed field did not exist yet, $push creates it as a one-element array:

{
"_id": "65f0b3d4e4b0a1c2e4b0a1d2",
"name": "Margaret",
"joined": 2023,
"fines": 0,
"borrowed": ["Compilers"]
}

When the book comes back, $pull removes it by value:

db.members.updateOne(
{ name: "Margaret" },
{ $pull: { borrowed: "Compilers" } }
)

The element is gone and the array is empty again:

{
"_id": "65f0b3d4e4b0a1c2e4b0a1d2",
"name": "Margaret",
"joined": 2023,
"fines": 0,
"borrowed": []
}

updateMany is the same call as updateOne, but it applies the change to every document the filter selects. Here we forgive one fine for everyone who currently owes something — note the negative $inc:

db.members.updateMany(
{ fines: { $gt: 0 } },
{ $inc: { fines: -1 } }
)

The counts now reflect everyone who was touched in this single operation:

{ "acknowledged": true, "matchedCount": 2, "modifiedCount": 2 }

In Compass: double-click any field value in the Documents tab to edit it inline, or use the Update button after entering a filter to apply an update document to many matches at once.

  • The most important gotcha: if you pass an update document with no operators — a plain document like the one you would insert — MongoDB treats it as a full replacement and wipes every field you did not include. Almost always you want $set, not a bare replacement.
  • matchedCount and modifiedCount can differ. A document can match the filter yet not be modified, because the new value equals the old one. Re-running an idempotent update is the classic case.
  • updateMany has no built-in limit — it changes every match. Double-check your filter before running one; a too-broad filter quietly rewrites more documents than you meant.
  • $set creates missing fields, $unset removes them, and $inc works even when the field is absent (it starts from zero). These operators are forgiving about whether the field already exists.
You call updateOne with a plain document and no operators. What happens?
matchedCount is 1 but modifiedCount is 0 after an update. Why?
Which operator removes a field from a document entirely?
How do you append one element to an array field without replacing the array?