W3docs

MongoDB Update

Learn how to update MongoDB documents in Python using update_one(), update_many(), upsert, and update operators like $set, $inc, and $push.

PyMongo provides two core methods for modifying documents in a collection: update_one() changes the first document that matches a filter, and update_many() changes every matching document. Both methods use MongoDB's update operators — instructions like $set, $inc, and $push — to describe exactly what should change, leaving the rest of the document untouched.

This chapter covers:

  • Connecting to MongoDB with PyMongo
  • update_one() — change a single document
  • update_many() — change many documents at once
  • Update operators: $set, $unset, $inc, $push, $pull
  • Updating nested fields with dot notation
  • Upserts — insert-or-update in one step
  • Checking what was modified with the result object

If you have not inserted any documents yet, read MongoDB Insert first. For filtering syntax, see MongoDB Query.

Connecting to MongoDB

Install the driver if you have not already:

pip install pymongo

Then connect to a database and collection:

import pymongo

client = pymongo.MongoClient("mongodb://localhost:27017/")
db = client["mydatabase"]
mycol = db["mycollection"]

MongoClient is lazy — it does not actually open a socket until the first operation. The database and collection are created automatically the first time a document is written to them.

Updating a Single Document

update_one(filter, update) finds the first document that matches filter and applies update to it.

result = mycol.update_one(
    {"name": "Alice"},       # filter: which document to change
    {"$set": {"age": 31}}    # update: what to change
)

print(result.matched_count)   # 1 if a document was found
print(result.modified_count)  # 1 if a value actually changed

The $set operator overwrites the listed fields and leaves every other field in the document exactly as it was. Without $set the second argument would replace the entire document, which is rarely what you want.

Why matched_count and modified_count can differ

If the document already has age: 31, MongoDB matches it but does not write anything. In that case matched_count is 1 and modified_count is 0. Always check modified_count to confirm a real write happened.

Updating Multiple Documents

update_many(filter, update) applies the same update to every document that matches the filter.

result = mycol.update_many(
    {"status": {"$ne": "closed"}},   # every document where status != "closed"
    {"$set": {"status": "open"}}
)

print(f"Matched:  {result.matched_count}")
print(f"Modified: {result.modified_count}")

update_many() is atomic per document but not across documents. Each individual document is modified atomically, but other clients can read or write between individual document updates within the same update_many() call.

Update Operators

MongoDB provides a set of update operators so you can express changes precisely without sending the entire document over the wire.

$set — set field values

# Set one field
mycol.update_one({"name": "Alice"}, {"$set": {"city": "Boston"}})

# Set multiple fields at once
mycol.update_one({"name": "Alice"}, {"$set": {"city": "Boston", "active": True}})

$unset — remove a field

# Remove the "temporary" field from the matching document
mycol.update_one({"name": "Alice"}, {"$unset": {"temporary": ""}})

The value you assign to the field inside $unset is irrelevant — MongoDB ignores it. The convention is to use an empty string.

$inc — increment or decrement a number

# Add 5 to the "score" field (creates it at 5 if it does not exist)
mycol.update_one({"name": "Alice"}, {"$inc": {"score": 5}})

# Subtract 1
mycol.update_one({"name": "Alice"}, {"$inc": {"score": -1}})

$inc is safe for counters because it is a single atomic operation — two simultaneous increments will both be applied correctly.

$push — append to an array

# Add a tag to the "tags" array (creates the array if it does not exist)
mycol.update_one({"name": "Alice"}, {"$push": {"tags": "python"}})

$pull — remove elements from an array

# Remove all occurrences of "python" from the "tags" array
mycol.update_one({"name": "Alice"}, {"$pull": {"tags": "python"}})

Updating Nested Documents

Use dot notation to reach into embedded documents without rewriting the entire nested object.

Consider a document with this shape:

{
  "name": "Alice",
  "address": {
    "city": "New York",
    "state": "NY"
  }
}

To change only the state field inside address:

mycol.update_one(
    {"name": "Alice"},
    {"$set": {"address.state": "NJ"}}
)

The city field inside address is not touched. You can go as many levels deep as your schema requires — for example "address.geo.lat".

Upsert: Insert or Update in One Step

An upsert tells MongoDB: "update this document if it exists; insert it if it does not." Pass upsert=True as a keyword argument:

result = mycol.update_one(
    {"name": "Bob"},
    {"$set": {"age": 25, "status": "new"}},
    upsert=True
)

if result.upserted_id:
    print(f"Inserted new document with _id: {result.upserted_id}")
else:
    print("Updated an existing document")

When MongoDB inserts a new document via upsert, the result object's upserted_id attribute holds the new _id. When it updates an existing document, upserted_id is None.

update_many() also accepts upsert=True. If no documents match, MongoDB inserts one new document — it never inserts multiple documents from a single upsert.

Inspecting the Result Object

Both update_one() and update_many() return an UpdateResult object with three useful attributes:

AttributeDescription
matched_countNumber of documents that matched the filter
modified_countNumber of documents that were actually changed
upserted_id_id of the newly inserted document (upsert only); None otherwise
result = mycol.update_many(
    {"role": "guest"},
    {"$set": {"role": "member"}}
)

print(f"Matched:  {result.matched_count}")
print(f"Modified: {result.modified_count}")
print(f"Upserted: {result.upserted_id}")

Common Gotchas

Forgetting an update operator replaces the document. This call deletes all fields except _id and replaces the document with just age: 31:

# Dangerous — replaces the whole document
mycol.update_one({"name": "Alice"}, {"age": 31})

Always wrap your changes in an operator ($set, $inc, etc.).

Filtering by _id requires an ObjectId. Document IDs are stored as ObjectId objects, not plain strings:

from bson.objectid import ObjectId

mycol.update_one(
    {"_id": ObjectId("64a1f2c3d4e5f6a7b8c9d0e1")},
    {"$set": {"verified": True}}
)

Passing the ID as a plain string will match nothing and silently do nothing.

Next Steps

Was this page helpful?