Updating & deleting documents
|
This section documents the current MongoDB 8.x server line as published at the MongoDB Server Manual, which is the reference these pages are written and verified against. No specific patch version is pinned. Some capabilities (Atlas Search, Atlas Vector Search, and parts of encryption and backup) are Atlas-only — they are linked, not documented in depth. This content was generated with the assistance of AI and should be verified against the official manual before being relied on in production, since MongoDB iterates quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
Writes that change existing data come in two shapes: operator updates, which modify named fields in place,
and full replacement, which swaps the whole document (except _id). This page covers the update and delete
methods, the field and array update operators, upserts, the findOneAndX family, and pipeline-form updates.
The manual’s overview is Update Documents.
updateOne, updateMany, replaceOne
updateOne(filter, update) modifies the first document matching filter; updateMany(filter, update)
modifies all of them. The filter is exactly the filter document from
Reading data with the Query API. An operator update uses $-prefixed
operators and touches only the named fields; a replacement passes a plain document and replaces every field
but _id. replaceOne is the dedicated method for the replacement case.
// operator update: only "status" and "updatedAt" change
db.orders.updateOne(
{ _id: 42 },
{ $set: { status: "shipped", updatedAt: new Date() } }
)
// updateMany: same change across many documents
db.orders.updateMany({ status: "open" }, { $set: { status: "cancelled" } })
// replaceOne: whole document swapped (keeps _id)
db.orders.replaceOne({ _id: 42 }, { customerId: 7, status: "shipped", lines: [] })
The methods return a write result describing what happened:
{
"acknowledged": true,
"matchedCount": 1,
"modifiedCount": 1,
"upsertedId": null
}
matchedCount counts documents that matched the filter; modifiedCount counts those actually changed (a
$set to the value a field already holds matches but does not modify).
Field update operators
The full list is Update Operators.
db.products.updateOne(
{ _id: "A-1" },
{
$set: { name: "Widget", "spec.color": "red" }, // set fields (creates if absent)
$unset: { legacyCode: "" }, // remove a field
$rename: { desc: "description" }, // rename a field
$inc: { views: 1, stock: -2 }, // add to numeric fields
$mul: { price: 1.1 }, // multiply a numeric field
$min: { lowestSeen: 9.99 }, // set only if given value is lower
$max: { highestSeen: 19.99 }, // set only if given value is higher
$currentDate: { updatedAt: true }, // set to current date/time
$setOnInsert: { createdAt: new Date() } // applied only on an upsert insert
}
)
$setOnInsert has no effect on a plain update; it only seeds fields when an upsert creates a new document
(see Upserts).
Array update operators
$push (with $each, $slice, $sort, $position)
// append one element
db.carts.updateOne({ _id: 1 }, { $push: { items: "sku-9" } })
// append several, keep the array sorted and capped at 10, inserting at index 0
db.feeds.updateOne(
{ _id: 1 },
{ $push: { entries: {
$each: [ { ts: 3 }, { ts: 1 } ],
$sort: { ts: 1 },
$slice: -10,
$position: 0
} } }
)
$addToSet
// add only if not already present (set semantics)
db.products.updateOne({ _id: "A-1" }, { $addToSet: { tags: { $each: ["sale", "new"] } } })
$pull and $pullAll
// $pull: remove every element matching a condition
db.carts.updateOne({ _id: 1 }, { $pull: { items: { qty: { $lte: 0 } } } })
// $pullAll: remove every listed value exactly
db.carts.updateOne({ _id: 1 }, { $pullAll: { skus: ["sku-1", "sku-2"] } })
Positional operators $, $[], $[<identifier>]
$ updates the first array element matched by the query filter. $[] updates every element.
$[<identifier>] updates every element matched by a matching entry in arrayFilters. These are documented at
$,
https://www.mongodb.com/docs/manual/reference/operator/update/positional-all/], and
$[<identifier>`].
// $ : bump the qty of the matched line only
db.orders.updateOne(
{ _id: 1, "lines.sku": "A-1" },
{ $set: { "lines.$.qty": 5 } }
)
// $[] : apply a discount to every line
db.orders.updateOne({ _id: 1 }, { $mul: { "lines.$[].price": 0.9 } })
// $[elem] with arrayFilters : only lines over 100
db.orders.updateOne(
{ _id: 1 },
{ $set: { "lines.$[big].flagged": true } },
{ arrayFilters: [ { "big.price": { $gt: 100 } } ] }
)
Upserts
Passing { upsert: true } makes an update that matches nothing insert a new document instead. The new
document is built from the equality conditions in the filter plus any $setOnInsert and other update
operators.
db.counters.updateOne(
{ _id: "invoice" }, // equality condition -> seeds _id
{
$inc: { seq: 1 }, // applied to the new document too
$setOnInsert: { createdAt: new Date() }
},
{ upsert: true }
)
If no invoice counter exists, the upsert inserts { _id: "invoice", seq: 1, createdAt: <now> }; the write
result then carries upsertedId. See
the upsert notes.
findOneAndUpdate, findOneAndReplace, findOneAndDelete
These modify (or delete) a single document and return a document in one atomic step. returnDocument
chooses which version comes back: "before" (the default) or "after". They also accept sort (to pick
which document when several match) and projection.
// atomically claim the next queued job and get it back
const job = db.jobs.findOneAndUpdate(
{ status: "queued" },
{ $set: { status: "running", startedAt: new Date() } },
{ sort: { priority: -1 }, returnDocument: "after" }
)
db.people.findOneAndReplace({ _id: 1 }, { name: "Ada", role: "admin" }, { returnDocument: "after" })
db.jobs.findOneAndDelete({ status: "done" }, { sort: { finishedAt: 1 } })
See findOneAndUpdate().
Update with an aggregation pipeline
The update argument can be an array — an aggregation pipeline ($set/$addFields, $unset, $replaceRoot,
$replaceWith). This allows an update that references other fields of the same document.
// total is computed from price and qty already in the document
db.orders.updateMany(
{},
[ { $set: { total: { $multiply: ["$price", "$qty"] },
updatedAt: "$$NOW" } } ]
)
The allowed stages and worked examples are in Update Documents with Aggregation Pipeline; the expression language is the same one described in Aggregation pipeline.
deleteOne and deleteMany
deleteOne(filter) removes the first matching document; deleteMany(filter) removes all of them. The filter
is again the query filter document. The walkthrough is
Delete Documents.
db.sessions.deleteOne({ _id: "s-1" })
db.sessions.deleteMany({ expiresAt: { $lt: new Date() } })
// delete every document, keep the collection, its indexes, and its options
db.sessions.deleteMany({})
The delete result reports how many were removed:
{ "acknowledged": true, "deletedCount": 128 }
drop() versus deleting all documents
db.collection.deleteMany({}) empties a collection but leaves the collection, its indexes, and its
configured options in place. db.collection.drop() removes the collection itself — including every index — and is typically much faster because it does not process documents one by one.
db.sessions.drop() // collection and its indexes are gone
See db.collection.drop().