MongoDB Update Documents

Change existing documents with updateOne and updateMany, use update operators such as $set, $inc, $unset, $rename, $min and $max, insert-or-update with upsert, replace whole documents, and get the updated document back with findOneAndUpdate.

Lesson 6 of 43Beginner17 min read

Data changes constantly: prices go up, stock goes down, orders move from "paid" to "shipped", users update their profiles. In MongoDB, you change documents with updateOne and updateMany. Each takes a filter, which chooses the documents, and an update document describing what to change.

The update document uses update operators. $set sets fields to new values, $inc adds to a number, $unset removes a field, and there are more for renaming fields, keeping minimums and maximums, recording timestamps and, as you'll see in the arrays lesson, changing array elements. Operators change only the fields you mention and leave the rest of the document alone.

Updates to a single document are atomic: if two requests run $inc on the same stock count at the same moment, both changes are applied correctly. In this lesson you'll use the main operators, read update results, create documents automatically with upsert, replace documents entirely, and fetch the updated document in one step with findOneAndUpdate.

updateOne and updateMany

MongoDB Shell
db.products.updateOne({ _id: 2 }, { $set: { price: 26 } })
// { acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }

db.products.updateMany({ category: 'kitchen' }, { $set: { onSale: true } })
  • updateOne changes the first matching document; updateMany changes all matches.
  • matchedCount is how many documents matched the filter; modifiedCount is how many actually changed. Setting a field to the value it already has counts as matched but not modified.
  • An update with an empty filter {} and updateMany changes every document, so double-check filters.

Update operators

OperatorEffectExample
$setSet fields, creating them if needed{ $set: { status: 'shipped' } }
$unsetRemove fields{ $unset: { tempCode: '' } }
$incAdd to a number (negative to subtract){ $inc: { 'stock.qty': -1 } }
$mulMultiply a number{ $mul: { price: 1.1 } }
$renameRename a field{ $rename: { colour: 'color' } }
$min, $maxUpdate only if the new value is lower or higher{ $max: { highScore: 980 } }
$currentDateSet a field to the current date{ $currentDate: { updatedAt: true } }

Several operators can be combined in one update, and dot notation updates fields inside embedded documents without replacing the whole sub-document.

Upserts, replacement and findOneAndUpdate

  • Upsert: with { upsert: true }, if nothing matches, MongoDB inserts a new document built from the filter and the update. $setOnInsert sets fields only when inserting, such as createdAt.
  • replaceOne(filter, newDocument) replaces everything except _id. It takes a plain document, not operators.
  • findOneAndUpdate updates a document and returns it. Pass { returnDocument: 'after' } to get the new version instead of the old one.

MongoDB CRUD Syntax

Update syntax

db.c.updateOne({ _id: 1 }, { $set: { status: 'paid' } })
db.c.updateMany({ status: 'trial' }, { $set: { status: 'expired' } })
db.c.updateOne({ _id: 1 }, { $inc: { views: 1 }, $currentDate: { updatedAt: true } })
db.c.updateOne({ _id: 1 }, { $unset: { tempToken: '' } })
db.c.updateOne({ email: e }, { $set: { name: n }, $setOnInsert: { createdAt: new Date() } }, { upsert: true })
db.c.replaceOne({ _id: 1 }, { name: 'New', price: 5 })
db.c.findOneAndUpdate({ _id: 1 }, { $inc: { qty: -1 } }, { returnDocument: 'after' })

MongoDB CRUD Summary

  • updateOne changes the first match; updateMany changes every match.
  • Update operators change only the named fields: $set, $unset, $inc, $mul, $rename, $min, $max, $currentDate.
  • matchedCount and modifiedCount report what happened.
  • upsert: true inserts when nothing matches; $setOnInsert applies only to inserts.
  • replaceOne swaps the whole document; findOneAndUpdate returns the document.

MongoDB CRUD Examples

Each example shows the code, the output it produces and what each part does.

Example 1

$set, $inc and update results

MongoDB Shell
db.products.updateOne({ _id: 2 }, { $set: { price: 26, onSale: false } })
db.products.updateOne({ _id: 2 }, { $set: { price: 26 } })
db.products.updateOne({ _id: 1 }, { $inc: { 'stock.qty': -3 } })
db.products.updateMany({ category: 'kitchen' }, { $mul: { price: 1.1 } })
db.products.find({ _id: { $in: [1, 2, 3] } }, { name: 1, price: 1, 'stock.qty': 1, _id: 0 })
Output
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 0,
  upsertedCount: 0
}
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 2,
  modifiedCount: 2,
  upsertedCount: 0
}
[
  { name: 'Mug', price: 9.350000000000001, stock: { qty: 9 } },
  { name: 'Lamp', price: 26, stock: { qty: 0 } },
  { name: 'Kettle', price: 32.989000000000004, stock: { qty: 5 } }
]
Line-by-line explanation
  1. Line 1$set changes the price and adds a new onSale field, leaving the rest untouched.
  2. Line 2The price is already 26, so the document matches but isn't modified.
  3. Line 3$inc with a negative number subtracts, here from a nested field.
  4. Line 4$mul multiplies the price of every kitchen product.
  5. Line 5Floating-point maths gives long decimals; for money, store whole pennies or Decimal128, covered in the data types lesson.
Example 2

$unset, $rename, $min, $max and timestamps

MongoDB Shell
db.players.insertOne({ _id: 'ada', score: 120, best: 300, colour: 'red', temp: 'x' })
db.players.updateOne({ _id: 'ada' }, {
  $unset: { temp: '' },
  $rename: { colour: 'color' },
  $max: { best: 250 },
  $min: { score: 100 },
  $set: { updatedAt: ISODate('2026-03-01T12:00:00Z') }
})
db.players.findOne({ _id: 'ada' })
db.players.updateOne({ _id: 'ada' }, { $max: { best: 410 } })
db.players.findOne({ _id: 'ada' }).best
Output
{ acknowledged: true, insertedId: 'ada' }
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{
  _id: 'ada',
  score: 100,
  best: 300,
  color: 'red',
  updatedAt: ISODate('2026-03-01T12:00:00.000Z')
}
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
410
Line-by-line explanation
  1. Line 3$unset removes the field completely.
  2. Line 4$rename changes a field's name and keeps its value.
  3. Line 5$max only changes best if the new value is higher; 250 isn't, so best stays 300.
  4. Line 6$min only changes score if the new value is lower; 100 is, so it updates.
  5. Line 7In real code, $currentDate or new Date() records when the change happened.
  6. Line 11A higher value replaces the old best score.
Example 3

Upserts and findOneAndUpdate

MongoDB Shell
const visit = (page) => db.pageviews.updateOne(
  { page },
  { $inc: { views: 1 }, $setOnInsert: { firstSeen: ISODate('2026-03-01T00:00:00Z') } },
  { upsert: true }
)
visit('/home').upsertedCount
visit('/home').upsertedCount
visit('/shop').upsertedCount
db.pageviews.find({}, { _id: 0 }).sort({ page: 1 })

db.stock.insertOne({ _id: 'MUG', qty: 3 })
db.stock.findOneAndUpdate({ _id: 'MUG', qty: { $gte: 2 } }, { $inc: { qty: -2 } }, { returnDocument: 'after' })
db.stock.findOneAndUpdate({ _id: 'MUG', qty: { $gte: 2 } }, { $inc: { qty: -2 } }, { returnDocument: 'after' })
Output
1
0
1
[
  {
    page: '/home',
    firstSeen: ISODate('2026-03-01T00:00:00.000Z'),
    views: 2
  },
  {
    page: '/shop',
    firstSeen: ISODate('2026-03-01T00:00:00.000Z'),
    views: 1
  }
]
{ acknowledged: true, insertedId: 'MUG' }
{ _id: 'MUG', qty: 1 }
null
Line-by-line explanation
  1. Lines 1 to 5An upsert updates the page's counter, or creates it if it doesn't exist yet.
  2. Line 3$setOnInsert sets firstSeen only when the document is created.
  3. Lines 6 to 8The first visit to each page inserts (upsertedCount 1); later visits only increment.
  4. Line 12The filter checks there is enough stock, and the update happens atomically in the same step.
  5. Line 13Only 1 is left, so nothing matches and findOneAndUpdate returns null: the stock can't go negative.

Common MongoDB CRUD Mistakes

Forgetting the update operator

Don't
db.users.updateOne({ _id: 1 }, { name: 'Ada' })   // error: update document requires atomic operators
Do
db.users.updateOne({ _id: 1 }, { $set: { name: 'Ada' } })

Replacing a nested document by accident

Don't
db.users.updateOne({ _id: 1 }, { $set: { address: { city: 'York' } } })   // removes street and postcode
Do
db.users.updateOne({ _id: 1 }, { $set: { 'address.city': 'York' } })

Read, change in code, write back

Don't
const p = db.products.findOne({ _id: 1 })
db.products.updateOne({ _id: 1 }, { $set: { 'stock.qty': p.stock.qty - 1 } })   // two buyers can overwrite each other
Do
db.products.updateOne({ _id: 1, 'stock.qty': { $gte: 1 } }, { $inc: { 'stock.qty': -1 } })

MongoDB CRUD in Real Projects

Updates in applications

Order systems move orders through statuses with $set and record when with $currentDate. Shopping carts and inventory use $inc with a guard in the filter so stock never goes negative. Analytics counters, rate limiters and "last seen" timestamps use upserts so the first event creates the document. Profile edit forms use $set with dot notation so only the edited fields change.

MongoDB CRUD FAQ

How do I add a new field to every document in a collection?

Run updateMany with an empty filter and $set: db.products.updateMany({}, { $set: { onSale: false } }). To add it only where it's missing, so existing values aren't overwritten, filter on { onSale: { $exists: false } }. Check matchedCount and modifiedCount in the result to confirm how many documents changed.

How do I update a field using the value of another field?

Pass an aggregation pipeline, an array of stages, as the update instead of a plain update document. For example, db.users.updateMany({}, [{ $set: { fullName: { $concat: ['$first', ' ', '$last'] } } }]) builds a field from two others. Inside the pipeline, '$first' means "the value of the first field", which ordinary $set can't do.

What is the difference between updateOne and replaceOne?

updateOne uses operators such as $set and $inc, so it changes only the fields you name. replaceOne takes a plain document and swaps the whole stored document for it, keeping only _id, so any field you leave out is gone. Use replaceOne when your application holds the complete new version of a document, and updateOne for everything else.

Why is modifiedCount 0 when matchedCount is 1?

The filter found the document, but the update didn't change anything, usually because the field already had that value. MongoDB doesn't rewrite a document when the result would be identical. If you expected a change, check the field names and types in your update, since a typo creates or targets a different field.

How do I rename a field in all MongoDB documents?

Use $rename with updateMany: db.products.updateMany({}, { $rename: { colour: 'color' } }). Documents that don't have the old field are left as they are. Remember to update any indexes, validation rules and application code that use the old name.

MongoDB CRUD Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the update to add 1 to the views count.

db.articles.updateOne({ _id: 9 }, { : { views: 1 } })
Multiple choiceeasy

What's the difference between updateOne and updateMany?

Predict the outputmedium

A product has price 10. What are matchedCount and modifiedCount after this?

db.products.updateOne({ _id: 7 }, { $set: { price: 10 } })
Multiple choicemedium

What does { upsert: true } do?

Multiple choicehard

How can you stop stock going below zero when two customers buy at once?

MongoDB CRUD Quiz

5 questions to check what you learned.

  1. Which operator removes a field?

    • $delete
    • $unset
    • $remove
    • $drop
  2. What does findOneAndUpdate with returnDocument: 'after' return?

    • The document after the update
    • The old document
    • The update result counts
    • Nothing
  3. What does $max: { best: 50 } do if best is 80?

    • Sets best to 50
    • Adds 50
    • Removes best
    • Leaves best at 80
  4. What does replaceOne keep from the original document?

    • All fields
    • Nothing
    • Nested documents
    • Only _id
  5. Why does { $set: { address: { city: 'York' } } } lose the street field?

    • $set can't handle nested documents
    • It replaces the whole address sub-document; use 'address.city' to change one field
    • It's a bug in MongoDB
    • city is a reserved word