Learn how to change existing MongoDB documents with updateOne, updateMany, replaceOne and findOneAndUpdate, and how to read the update result.

Bishal Biswas
Self Learner
What you will learn
By the end of this lesson you will know how to:
- Change one document with
updateOne()and the$setoperator - Change many documents at once with
updateMany() - Read the result:
matchedCountandmodifiedCount - Swap a whole document with
replaceOne() - Update a document and get it back in one step with
findOneAndUpdate()
Data rarely stays the same. Prices go up, stock goes down, a customer changes their email. Updating is how you change documents that are already saved.
Get the sample data ready
This lesson changes data, so start from a clean copy. Run this in mongosh (you can run it again any time to reset):
use shop
db.products.deleteMany({})
db.products.insertMany([
{ name: "Notebook", category: "stationery", price: 49, stock: 120, tags: ["paper", "school"] },
{ name: "Gel Pen", category: "stationery", price: 15, stock: 500, tags: ["pen", "school"] },
{ name: "Headphones", category: "electronics", price: 1999, stock: 35, tags: ["audio", "wireless"], rating: 4.5 },
{ name: "USB Cable", category: "electronics", price: 299, stock: 0, tags: ["cable"] },
{ name: "Coffee Mug", category: "kitchen", price: 249, stock: 60, tags: ["ceramic", "gift"], rating: 4.8 }
])
The shape of an update
Every update needs two things:
- A filter: which document(s) to change. This is the same kind of filter you use with
find(). - An update: what to change. This uses update operators, which start with
$.
db.collection.updateOne(filter, update)
Think of it like this: An update is like a note to an office assistant: “Find the file for Notebook (the filter) and change the price to 59 (the update).” You don’t rewrite the whole file. You just correct one line.
Your first update with $set
$set is the most common update operator. It sets a field to a new value.
Let’s raise the price of the Notebook from 49 to 59:
db.products.updateOne(
{ name: "Notebook" }, // filter: which document
{ $set: { price: 59 } } // update: what to change
)
mongosh replies with a result:
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
Let’s check:
db.products.findOne({ name: "Notebook" })
// → { _id: ObjectId("..."), name: "Notebook", category: "stationery", price: 59, stock: 120, tags: [ "paper", "school" ] }
Only price changed. Every other field is exactly as it was.
Reading the result
The result tells you what happened. The two numbers that matter most are:
| Field | Meaning |
|---|---|
matchedCount |
How many documents matched your filter |
modifiedCount |
How many documents were actually changed |
acknowledged |
true means the server received and confirmed the write |
upsertedCount, insertedId |
Used by upserts, covered in the next lesson |
Why would matchedCount and modifiedCount be different? Run the same update again:
db.products.updateOne({ name: "Notebook" }, { $set: { price: 59 } })
// → { acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 0, upsertedCount: 0 }
The Notebook matched, but its price was already 59, so nothing needed to change. MongoDB is smart enough to skip it.
And if nothing matches the filter at all:
db.products.updateOne({ name: "Stapler" }, { $set: { price: 99 } })
// → { acknowledged: true, insertedId: null, matchedCount: 0, modifiedCount: 0, upsertedCount: 0 }
No error, just zero. This is a quiet failure, so in apps you should check matchedCount when you expect a match.
Tip:
matchedCount: 0usually means a typo in the filter: a wrong name, wrong capital letter, or a number stored as text.
Changing several fields at once
Put more fields inside $set. They all change together:
db.products.updateOne(
{ name: "USB Cable" },
{ $set: { stock: 80, price: 279 } }
)
// → matchedCount: 1, modifiedCount: 1
$set can also add new fields
If the field doesn’t exist yet, $set creates it. The Gel Pen has no rating, so let’s give it one:
db.products.updateOne({ name: "Gel Pen" }, { $set: { rating: 4.1 } })
db.products.findOne({ name: "Gel Pen" }, { name: 1, rating: 1, _id: 0 })
// → { name: "Gel Pen", rating: 4.1 }
This is the flexible schema at work. No table change is needed.
Updating by _id
updateOne() changes only the first document that matches. If several products had the name “Notebook”, you can’t be sure which one gets updated.
The safest way to target exactly one document is by its _id, which is always unique:
const mug = db.products.findOne({ name: "Coffee Mug" })
db.products.updateOne({ _id: mug._id }, { $set: { stock: 55 } })
// → matchedCount: 1, modifiedCount: 1
In real apps you usually have the _id already, for example from a URL like /products/6718a2f1....
Note: From MongoDB 8.0,
updateOne()also accepts asortoption, so you can choose which match gets updated, for example{ sort: { price: 1 } }to update the cheapest match.
Updating many documents with updateMany()
updateMany() has the same shape, but changes every document that matches.
Let’s put all stationery on sale:
db.products.updateMany(
{ category: "stationery" },
{ $set: { onSale: true } }
)
// → { acknowledged: true, insertedId: null, matchedCount: 2, modifiedCount: 2, upsertedCount: 0 }
Both the Notebook and the Gel Pen now have onSale: true.
An empty filter {} matches every document in the collection:
// Add a "lastChecked" date to every product
db.products.updateMany({}, { $set: { lastChecked: new Date() } })
// → matchedCount: 5, modifiedCount: 5
Warning: Before running
updateMany(), run the same filter withfind()orcountDocuments()first. It’s a cheap way to make sure you’re about to change the documents you think you are.
db.products.countDocuments({ category: "stationery" }) // check first
// → 2
Replacing a whole document with replaceOne()
Sometimes you want to throw away the old content and put in something new. replaceOne() does that. The second argument is a plain document, with no $ operators:
db.products.replaceOne(
{ name: "USB Cable" },
{ name: "USB-C Cable", category: "electronics", price: 349, stock: 40 }
)
// → matchedCount: 1, modifiedCount: 1
Now look at it:
db.products.findOne({ name: "USB-C Cable" })
// → { _id: ObjectId("..."), name: "USB-C Cable", category: "electronics", price: 349, stock: 40 }
Notice two things:
- The
_idstayed the same. MongoDB keeps it when replacing. - The
tagsfield is gone. Anything you don’t include in the replacement is removed.
updateOne() with $set |
replaceOne() |
|
|---|---|---|
| Second argument | Update operators like { $set: {...} } |
A plain document |
| Fields you don’t mention | Kept as they were | Removed |
_id |
Kept | Kept |
| Typical use | Change a few fields | Save a full edited form |
Tip: If you’re unsure, use
updateOne()with$set. It’s much harder to lose data by accident.
Update and read in one step: findOneAndUpdate()
Sometimes you want to change a document and see it straight away. findOneAndUpdate() does both in one atomic step, which means no other write can sneak in between the change and the read.
By default it returns the document before the change:
db.products.findOneAndUpdate(
{ name: "Headphones" },
{ $set: { stock: 30 } }
)
// → { _id: ObjectId("..."), name: "Headphones", ..., stock: 35, ... } (old value)
Add returnDocument: "after" to get the updated version instead:
db.products.findOneAndUpdate(
{ name: "Headphones" },
{ $set: { stock: 25 } },
{ returnDocument: "after", projection: { name: 1, stock: 1, _id: 0 } }
)
// → { name: "Headphones", stock: 25 }
You can also pass projection (as above) and sort in the same options object. If nothing matches, it returns null.
You can’t change _id
The _id field is permanent. Trying to change it gives an error:
db.products.updateOne({ name: "Gel Pen" }, { $set: { _id: 123 } })
// → MongoServerError: Performing an update on the path '_id' would modify the immutable field '_id'
If you truly need a different _id, insert a new document and delete the old one.
Updates are safe for a single document
When an update changes several fields in one document, MongoDB applies them all together or not at all. Another user will never see a half-updated product with the new price but the old stock. This is called being atomic at the document level. Updates that span many documents need transactions, which you’ll meet in the Advanced section.
SQL comparison
UPDATE products SET price = 59 WHERE name = 'Notebook';
UPDATE products SET on_sale = TRUE WHERE category = 'stationery';
db.products.updateOne({ name: "Notebook" }, { $set: { price: 59 } })
db.products.updateMany({ category: "stationery" }, { $set: { onSale: true } })
In application code
The Node.js driver uses the same method names and returns the same result fields. You’ll set it up in the Connecting MongoDB to Your First App lesson:
// Node.js driver
const result = await db.collection("products").updateOne(
{ name: "Notebook" },
{ $set: { price: 59 } }
);
if (result.matchedCount === 0) {
console.log("No product called Notebook!");
}
Common mistakes
- Forgetting
$set.updateOne({ name: "Notebook" }, { price: 59 })fails withMongoInvalidArgumentError: Update document requires atomic operators. Wrap the change:{ $set: { price: 59 } }. - Using
$setwithreplaceOne(). The replacement must be a plain document. Operators there cause a “must not contain atomic operators” error. - Expecting
updateOne()to change all matches. It changes only the first. UseupdateMany()for all of them. - Not checking
matchedCount. A typo in the filter updates nothing and raises no error. - Using the old
update()method. It’s deprecated. UseupdateOne(),updateMany()orreplaceOne().
Practice
Reset the data first using the setup block at the top.
- Set the price of the Gel Pen to 20.
- Mark every electronics product with
featured: true. - Replace the Coffee Mug with
{ name: "Coffee Mug", category: "kitchen", price: 299, stock: 50 }. Which fields disappeared? - Set the stock of the Notebook to 100 and get the updated document back.
Show a possible answer
// 1. One document, one field
db.products.updateOne({ name: "Gel Pen" }, { $set: { price: 20 } })
// → matchedCount: 1, modifiedCount: 1
// 2. Many documents
db.products.updateMany({ category: "electronics" }, { $set: { featured: true } })
// → matchedCount: 2, modifiedCount: 2
// 3. Replace: tags and rating are removed, _id is kept
db.products.replaceOne(
{ name: "Coffee Mug" },
{ name: "Coffee Mug", category: "kitchen", price: 299, stock: 50 }
)
// 4. Update and return the new version
db.products.findOneAndUpdate(
{ name: "Notebook" },
{ $set: { stock: 100 } },
{ returnDocument: "after" }
)
// → { _id: ObjectId("..."), name: "Notebook", ..., stock: 100, ... }
Key takeaways
updateOne(filter, update)changes the first match;updateMany()changes every match.- Updates use operators.
$setchanges a field’s value, or adds the field if it’s missing. matchedCounttells you how many documents matched;modifiedCounttells you how many really changed.replaceOne()swaps the whole document (except_id). Fields you leave out are deleted.findOneAndUpdate()changes one document and returns it. UsereturnDocument: "after"to see the new version.
Next lesson: Update Operators and Upserts
Was this explanation helpful?
Your feedback helps us improve the content for everyone.
Thank you for your feedback!
We appreciate you helping us improve.
What could we improve? (Optional)
Read aloud
Ready