MongoDB replaceOne

Learn how to replace documents with replaceOne in MongoDB. Understand the syntax, behavior, and practical use cases with hands-on examples.

Focus: replace documents with replaceone

Sponsored

You've built collections, inserted documents, updated fields with $set and $inc, and removed documents you no longer need. But what happens when a document's structure itself is outdated — not just a field value? Patching individual fields with update operators can get tedious and error-prone, especially when an entire document represents a snapshot of a real-world entity that has changed completely. That's exactly the pain point MongoDB's replaceOne method solves: it lets you swap out an entire document in a single atomic operation, giving you a clean slate without deleting and re-inserting. In this lesson, you'll master replaceOne — from its syntax and behavior to practical use cases and common pitfalls — so you can confidently replace documents with replaceone (yes, the method name is case-sensitive) in your MongoDB applications.

The problem this lesson solves

Imagine you have a users collection where each document stores profile information like name, email, and preferences. Over time, your application evolves: you add new fields, remove legacy ones, and change data shapes. Using updateOne with $set works fine for small changes, but what if you need to replace the entire document because the old structure is completely obsolete?

Doing it manually would mean deleting the old document and inserting a new one — a two-step process that risks leaving your data in an inconsistent state if something fails in between. You'd also lose the _id unless you manually preserve it.

MongoDB's replaceOne solves this by allowing you to replace an entire document in a single atomic operation. This means the replacement is all-or-nothing: either the old document is swapped for the new one, or nothing happens. No intermediate states, no orphaned data.

But there's a catch: replaceOne is not a shortcut for partial updates. It replaces the entire document — all fields not included in the replacement are removed. Understanding this distinction is critical to avoiding data loss, and that's what this lesson will make clear.

Core concept / mental model

Think of a MongoDB document as a file folder with multiple pages. updateOne with $set lets you edit a single page — change a name, add a note — while keeping the folder intact. replaceOne, on the other hand, throws the whole folder away and puts a brand-new folder in its place. The only thing that stays is the label (the _id field).

Here's the formal definition:

  • replaceOne(filter, replacement, options) — finds the first document that matches the filter, replaces it with the replacement document, and returns a result object with metadata.

Key characteristics:

  • Atomic operation — the replacement happens in one server-side step.
  • Preserves _id — MongoDB keeps the original _id value automatically, even though you don't include it in the replacement (if you include _id, it must match the original).
  • Replaces all fields — anything not in the replacement document is gone.
  • Only replaces the first match — if multiple documents match the filter, only the first one (in natural order) is replaced.
  • If no match — nothing is replaced; you can optionally insert a new document with upsert: true.

Pro tip: Think of replaceOne as the "nuclear option" for document updates. Use it when the new data is a complete snapshot of the entity, not a small change.

What replaceOne is not

  • It's not updateOne with a replacement object — update operators ($set, $inc) are not allowed in the replacement document. If you try, MongoDB throws an error.
  • It's not a partial update — even if you want to keep most fields, you must include them explicitly in the replacement.

How it works step by step

Let's walk through what happens when you call replaceOne:

  1. Construct a filter — a query document that identifies which document to replace. This uses the same syntax as find() (e.g., { _id: ObjectId('...') }).
  2. Write the replacement document — a plain document with the new field values. Do not include update operators like $set. You can include _id only if it matches the original document's _id.
  3. Call replaceOne with optional settings — e.g., { upsert: true } to insert if no match is found.
  4. Inspect the result object — it contains matchedCount and modifiedCount (or upsertedCount if upsert happened) so you know what actually occurred.

Let's see the result object in detail:

  • matchedCount — how many documents matched the filter (0 or 1).
  • modifiedCount — how many documents were actually replaced (0 or 1). If the replacement is identical to the original, this may be 0.
  • upsertedCount — if upsert: true and no match was found, this is 1 (and upsertedId will contain the new _id).

Hands-on walkthrough

Let's put theory into practice. We'll use a product_catalog collection to demonstrate. First, insert a sample document:

// Connect to MongoDB (assuming mongosh or a driver)
db.product_catalog.insertOne({
  _id: ObjectId('65f2a1b2c3d4e5f6a7b8c9d0'),
  sku: 'TSHIRT-001',
  name: 'Classic Tee',
  price: 19.99,
  category: 'apparel',
  tags: ['cotton', 'basic'],
  stock: 100,
  createdAt: new Date()
});

Now, we need to replace the entire product document with a new version that has completely different fields (e.g., we're rebranding it):

const filter = { sku: 'TSHIRT-001' };
const replacement = {
  sku: 'TSHIRT-001',
  name: 'Signature Tee',
  price: 29.99,
  isActive: true
};

const result = db.product_catalog.replaceOne(filter, replacement);
printjson(result);

Expected output:

{
  acknowledged: true,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}

Now check the document:

db.product_catalog.findOne({ sku: 'TSHIFT-001' });

Output:

{
  _id: ObjectId('65f2a1b2c3d4e5f6a7b8c9d0'),
  sku: 'TSHIFT-001',
  name: 'Signature Tee',
  price: 29.99,
  isActive: true
}

Notice: category, tags, stock, and createdAt are gone — they were not in the replacement. The _id remained the same.

Using upsert to insert if missing

If you want to replace an existing document or insert a new one when no match is found, use { upsert: true }:

const filter = { sku: 'COFFEE-002' };
const replacement = {
  sku: 'COFFEE-002',
  name: 'Dark Roast Beans',
  price: 14.50,
  origin: 'Colombia'
};

const result = db.product_catalog.replaceOne(filter, replacement, { upsert: true });
printjson(result);

Expected output (since no match):

{
  acknowledged: true,
  matchedCount: 0,
  modifiedCount: 0,
  upsertedCount: 1,
  upsertedId: ObjectId('...')
}

Now a new document is inserted with an auto-generated _id.

Compare options / when to choose what

How does replaceOne differ from updateOne, findOneAndReplace, and the manual delete+insert approach? Here's a quick comparison:

Method What it does Preserves _id Can partially update? Atomic? Use case
updateOne with $set Patches specific fields Yes Yes Yes Small changes to one field
replaceOne Replaces entire document (except _id) Yes No Yes Full document snapshot updates
findOneAndReplace Replaces and returns the document (old or new) Yes No Yes When you need the result immediately
Delete + Insert Removes and recreates No N/A Not atomic Legacy/custom scripts (avoid if possible)

When to choose replaceOne over updateOne:

  • Choose replaceOne when the document's structure is fundamentally changing — new shape, different set of fields.
  • Choose updateOne when you only need to change a few field values while keeping the rest.
  • Choose findOneAndReplace when you need the replaced document returned in the same operation (e.g., to use in your application logic).

Pro tip: If you find yourself writing updateOne with dozens of $set fields, consider replaceOne — a clean replacement is often more readable and maintainable.

Troubleshooting & edge cases

Error: "The replacement document must not contain atomic operators"

If you try to use $set or $inc in the replacement, MongoDB throws a WriteError. For example:

// Wrong: will fail
db.product_catalog.replaceOne({ sku: 'TSHIFT-001' }, { $set: { price: 25 } });

Fix: Use updateOne with $set, or write the replacement as a plain document.

Problem: The _id in the replacement doesn't match the original

If you include an _id field in the replacement that differs from the original document's _id, MongoDB throws an error. For example:

// Wrong: different _id
const replacement = { _id: ObjectId('different'), ... };

Fix: Omit _id from the replacement, or ensure it matches exactly. If you need a new _id, delete the old document and insert a new one.

Problem: modifiedCount is 0 even though the filter matched

MongoDB may detect that the replacement is identical to the existing document and skip the actual write. For example:

const result = db.product_catalog.replaceOne({ sku: 'TEER-001' }, { name: 'Classic Tee', ... }); // same fields
printjson(result.modifiedCount); // might be 0

Fix: This is expected behavior. Check matchedCount to see if a match was found, and treat modifiedCount as "actual write performed."

Problem: Only the first matching document is replaced

replaceOne replaces only the first document that matches the filter, not all. If you expect to replace multiple documents, you'll need a loop or replaceMany (which doesn't exist — you'll need to use bulkWrite or iterate).

// Only one document updated
db.product_catalog.replaceOne({ category: 'apparel' }, { ... });

Fix: Use updateMany with update operators, or write a cursor loop that calls replaceOne for each document.

What you learned & what's next

You've just added a powerful tool to your MongoDB toolkit. You learned that replace documents with replaceone is a way to swap out an entire document atomically while preserving the _id. You now understand:

  • The mental model of replacing a folder with a new one, keeping the label.
  • How to use replaceOne(filter, replacement, options) in practice.
  • The importance of omitting update operators in the replacement.
  • How to use upsert to insert when no match is found.
  • When to choose replaceOne over updateOne or findOneAndReplace.
  • How to handle common errors like using $set in replacement or _id mismatches.

You're ready to apply replaceOne in your own projects — whether you're migrating a data model or syncing external system snapshots into MongoDB.

Next in the track, you'll explore bulk write operations to efficiently perform multiple replacements and updates in a single call. This will take your MongoDB proficiency to the next level by reducing network round-trips and making your code faster and more maintainable. Keep going!

Practice recap

Now it's your turn: Using the product_catalog collection from this lesson, practice replacing a document that has an entirely new shape but the same _id. Try using upsert: true with a non-existent SKU and check the result object. Then, deliberately attempt to use $set inside a replacement and observe the error — this will make the rule stick.

Common mistakes

  • Using update operators like $set inside the replacement document — MongoDB throws a WriteError. Remember: replaceOne expects a plain document, not an update expression.
  • Forgetting that replaceOne replaces ALL fields — if you omit fields from the replacement, they are lost. Always include every field you want to keep.
  • Changing the _id in the replacement document — MongoDB will throw an error if the _id doesn't match the original. Omit it or keep it identical.
  • Assuming replaceOne updates all matching documents — it only replaces the first match. Use updateMany for multiple documents.
  • Ignoring matchedCount and modifiedCount in the result — these tell you whether a match occurred and whether a write actually happened, crucial for debugging.

Variations

  1. Use findOneAndReplace when you need the replaced document returned immediately, e.g., to use in your application logic.
  2. Use updateOne with update operators when you only need to modify a few fields — replacing the whole document would be wasteful.
  3. Use bulkWrite to perform multiple replaceOne operations in a single call for better performance.

Real-world use cases

  • Syncing a user's profile from an external CRM — replace the entire user document with the latest snapshot each time, preserving _id.
  • Rebranding a product catalog — replacing outdated product documents with new structures (new fields, removed legacy ones) in one atomic operation.
  • Resetting a session or state document — when a user's session needs a fresh start, replace the whole document with initial values instead of patching many fields.

Key takeaways

  • replaceOne swaps an entire document in a single atomic operation, preserving the _id.
  • The replacement document must be a plain object — no update operators like $set allowed.
  • All fields not in the replacement are removed; include every field you want to keep.
  • Only the first matching document is replaced; upsert: true inserts if no match is found.
  • Use matchedCount and modifiedCount to verify the operation's outcome.
  • Choose replaceOne for full-document snapshot updates, updateOne for partial modifications.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.