0Pricing
MongoDB Academy · Lesson

updateOne and updateMany With $set and $unset

Learners will modify specific fields in one or many documents without replacing the entire document.

updateOne and updateMany With $set and $unset is a free MongoDB Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the MongoDB Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Two Main Update Methods

MongoDB provides two primary methods for modifying existing documents:

  • updateOne(filter, update, options) — modifies the first document matching the filter. If multiple documents match, only one is updated.
  • updateMany(filter, update, options) — modifies all documents matching the filter.

Both methods return a result object with matchedCount (how many documents matched the filter) and modifiedCount (how many were actually changed—some matching documents may already have the target value). Always verify modifiedCount in your application logic.

// updateOne: modify a specific document
const result = await db.collection('users').updateOne(
  { email: 'alice@example.com' },          // filter
  { $set: { lastLoginAt: new Date() } }    // update
);
console.log(result.matchedCount);   // 1
console.log(result.modifiedCount);  // 1

// updateMany: modify all matching documents
const bulkResult = await db.collection('users').updateMany(
  { emailVerified: false },
  { $set: { needsVerification: true } }
);

$set: Modifying Specific Fields

The $set operator sets the value of one or more fields. If a field does not exist, $set creates it. If it already exists, $set replaces its value. Only the specified fields are changed—all other fields in the document remain untouched.

This is the most important update operator and the one you will use in the vast majority of update operations. Never pass a plain document as the update argument without an operator—doing so replaces the entire document, losing all other fields.

// $set updates only the specified fields
db.users.updateOne(
  { _id: userId },
  { $set: { name: 'Alice Smith', updatedAt: new Date() } }
);
// Only 'name' and 'updatedAt' change; all other fields are preserved

// $set creates the field if it doesn't exist:
db.users.updateOne(
  { _id: userId },
  { $set: { newsletterOptIn: true } }  // Added to doc even if it wasn't there
);

// $set on a nested field:
db.users.updateOne(
  { _id: userId },
  { $set: { 'address.city': 'New York' } }  // Dot notation for nested

The Whole-Document Replacement Trap

A critical MongoDB beginner mistake: if you pass a plain document as the update argument—without any update operators—MongoDB treats it as a full document replacement. The matched document is replaced entirely with the new document (preserving only _id). All other fields are lost.

This behavior is actually intentional and useful for replacing an entire document, but it is a common source of accidental data loss. Always use update operators ($set, $inc, etc.) unless you explicitly intend to replace the full document.

// DANGER: This REPLACES the entire document!
db.users.updateOne(
  { _id: userId },
  { name: 'Alice Smith' }  // No $set operator!
);
// Before: { _id: ..., name: 'Alice', email: 'alice@...', age: 28 }
// After:  { _id: ..., name: 'Alice Smith' }  <-- email and age are GONE!

// CORRECT: Use $set to modify only name
db.users.updateOne(
  { _id: userId },
  { $set: { name: 'Alice Smith' } }  // Only name changes
);

$unset: Removing Fields

The $unset operator removes one or more fields from a document. The value you assign in $unset is ignored—conventionally set to '' or 1. The field is completely removed from the document, not set to null.

Use $unset when you want to clean up legacy fields during a schema migration, remove sensitive data (like a password reset token after it is used), or strip fields that should no longer be stored on certain documents.

// Remove specific fields from a document
db.users.updateOne(
  { _id: userId },
  { $unset: { passwordResetToken: '', passwordResetExpiry: '' } }
);
// Both fields are completely removed from the document

// $unset during schema migration: remove legacy field from all docs
db.users.updateMany(
  { legacyField: { $exists: true } },
  { $unset: { legacyField: '' } }
);
// Removes 'legacyField' from every document that has it

Combining $set and $unset

You can combine multiple update operators in a single updateOne or updateMany call. They are applied atomically in a single operation—there is no window where the document is in a partially updated state.

This is important for schema migrations where you simultaneously add a new field and remove an old one. Doing it in one atomic operation prevents any client from reading a document in the transitional state where both old and new fields exist.

// Atomic schema migration: rename 'displayName' to 'username'
// (set new field, copy value from old field, remove old field)
db.users.updateMany(
  { displayName: { $exists: true } },
  {
    $rename: { displayName: 'username' }  // Atomic rename
    // $rename is equivalent to $set + $unset in one operator
  }
);

// Or manually with $set + $unset + $currentDate:
db.orders.updateOne(
  { _id: orderId },
  {
    $set: { status: 'shipped', trackingNumber: 'UPS123' },
    $unset: { processingNote: '' },
    $currentDate: { shippedAt: true }
  }
);

$rename: Renaming Fields

The $rename operator atomically renames a field within a document. It is equivalent to $seting the new name with the old value and then $unseting the old name—but done in a single atomic step.

Use $rename when refactoring your schema and changing field names. It works correctly even when the destination field does not exist. If the source field does not exist, $rename does nothing (no error). It cannot rename fields across embedded documents.

// Rename 'fname' and 'lname' to 'firstName' and 'lastName'
db.users.updateMany(
  {},   // All documents
  { $rename: { fname: 'firstName', lname: 'lastName' } }
);

// Before: { _id: ..., fname: 'Alice', lname: 'Smith' }
// After:  { _id: ..., firstName: 'Alice', lastName: 'Smith' }

// If source field doesn't exist, $rename silently skips
// No error thrown

$currentDate: Setting Timestamps

The $currentDate operator sets a field to the current date/time at the moment the update is executed on the server. Using server-side timestamps is more reliable than passing a date from the application, because it avoids clock skew between multiple application servers.

Set the value to true to use the BSON Date type, or { $type: 'timestamp' } for the BSON Timestamp type (used internally by the replication system). In application code, BSON Date is almost always what you want.

// Set multiple timestamp fields atomically
db.orders.updateOne(
  { _id: orderId, status: 'pending' },
  {
    $set: { status: 'processing' },
    $currentDate: {
      processingStartedAt: true,       // BSON Date
      lastModified: true
    }
  }
);
// processingStartedAt = server-side current time
// More reliable than Date.now() from application

The upsert Option

Setting { upsert: true } in the options tells MongoDB: if no document matches the filter, insert a new document that combines the filter fields and the update. This is the upsert (update-or-insert) pattern—useful for 'insert if not exists, update if exists' semantics without a separate read operation.

The inserted document contains the fields from both the filter and the $set. Check result.upsertedId to see if an insert occurred. Upserts are atomic—no race condition between checking existence and inserting.

// Upsert: update if exists, insert if not
const result = await db.collection('userPreferences').updateOne(
  { userId: userId },                          // filter
  {
    $set: { theme: 'dark', language: 'en' },  // update
    $setOnInsert: { createdAt: new Date() }    // only on insert
  },
  { upsert: true }                             // option
);

if (result.upsertedId) {
  console.log('New preferences created:', result.upsertedId);
} else {
  console.log('Existing preferences updated');
}

updateMany: Bulk Field Changes

updateMany applies the same update to all matching documents in one operation. This is how you perform bulk schema migrations, add default values to missing fields, or update a batch of records based on a condition.

Be careful with updateMany({}, ...) (empty filter)—it modifies every document in the collection. Always include a filter that limits the scope. Consider adding a condition like { newField: { $exists: false } } to only process documents that need the change.

// Add default value to all existing docs missing the field
await db.collection('products').updateMany(
  { featured: { $exists: false } },   // Only docs without the field
  { $set: { featured: false } }       // Set default
);

// Batch status update
await db.collection('orders').updateMany(
  { status: 'processing', createdAt: { $lt: new Date(Date.now() - 86400000 * 7) } },
  { $set: { status: 'stalled', stalledAt: new Date() } }
);
// Flag orders processing for more than 7 days

findOneAndUpdate: Atomic Read-Modify

findOneAndUpdate(filter, update, options) atomically finds a document, updates it, and returns it in a single operation. This eliminates the race condition that exists when you do a separate findOne followed by an updateOne.

By default it returns the document before the update. Set { returnDocument: 'after' } to get the updated version. Combine with upsert: true to find-or-create patterns. This is ideal for claiming queue items, implementing counters, and any pattern where you need to both modify and read the document atomically.

// Claim the next pending job atomically
const job = await db.collection('jobs').findOneAndUpdate(
  { status: 'pending' },                    // filter
  {
    $set: { status: 'claimed', workerId: workerId, claimedAt: new Date() }
  },
  {
    sort: { priority: -1, createdAt: 1 },   // Pick highest priority, oldest
    returnDocument: 'after'                  // Return updated document
  }
);

if (job) {
  await processJob(job);  // job is fully updated in DB already
}

Update Operators Reference Summary

MongoDB provides many update operators beyond and . Here is a quick reference of the most important ones you will encounter:

  • : Set field value (create if absent)
  • : Remove a field completely
  • : Rename a field atomically
  • : Increment/decrement a numeric field
  • : Multiply a numeric field
  • /: Conditionally update to smaller/larger value
  • /: Add/remove array elements
  • : Add to array only if unique
  • : Set to server-side current date/time
// Multiple operators in one atomic update
db.userStats.updateOne(
  { userId: userId },
  {
    :         { lastSeen: new Date(), status: 'active' },
    :         { pageViews: 1, sessionCount: 1 },
    :         { longestSession: sessionDuration },
    :    { visitedPages: currentPage },
    : { updatedAt: true }
  },
  { upsert: true }
);
// All six operators apply atomically in one round trip

Quick Check

Test your understanding of MongoDB & NoSQL Databases concepts from this lesson.

Lesson Recap

In this lesson you learned: $set modifies specific fields without touching others—always use it rather than passing a bare document to avoid accidental full-document replacement, $unset removes fields entirely and is essential for schema migrations and cleaning up sensitive temporary data like password reset tokens, and upsert: true combined with $setOnInsert enables atomic find-or-create patterns without race conditions. Next up we explore numeric mutation operators: $inc, $mul, $min, and $max.

Frequently asked questions

Is the “updateOne and updateMany With $set and $unset” lesson free?

Yes — the full text of “updateOne and updateMany With $set and $unset” is free to read here on the web, and the MongoDB Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the MongoDB Academy course, upgrade to CoddyKit PRO.

What will I learn in “updateOne and updateMany With $set and $unset”?

Learners will modify specific fields in one or many documents without replacing the entire document. You practise MongoDB Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start MongoDB Academy?

No prior experience is required. MongoDB Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “updateOne and updateMany With $set and $unset” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this MongoDB Academy lesson?

Yes. Every MongoDB Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. updateOne and updateMany With $set and $unset
  2. Increment, Multiply, and Min/Max Operators
  3. Array Update Operators: $push, $pull, $addToSet
  4. Deleting Documents Safely With deleteOne and deleteMany
← Back to MongoDB Academy