MongoDBUpdate Documents

Update Documents

MongoDB provides updateOne(), updateMany(), and replaceOne() for modifying documents. Unlike SQL's UPDATE statement, MongoDB updates use update operators that describe how to modify fields — making updates surgical and avoiding full-document rewrites.

updateOne()

updateOne() modifies the first document that matches the filter. It takes two required arguments — a filter document and an update document — plus an optional options object.

updateOne() — change a single field

JS
const { MongoClient, ObjectId } = require('mongodb');
const client = new MongoClient('mongodb://localhost:27017');
const db = client.db('mydb');
const users = db.collection('users');

// Update email for a specific user
const result = await users.updateOne(
  { _id: new ObjectId('64a1f2c3d4e5f6a7b8c9d0e1') }, // filter
  { $set: { email: 'newemail@example.com' } }           // update
);

// Update by a field value
await users.updateOne(
  { username: 'alice' },
  { $set: { lastLogin: new Date(), active: true } }
);

updateOne() result

JS
{
  acknowledged: true,
  matchedCount: 1,    // number of documents that matched the filter
  modifiedCount: 1,   // number of documents actually changed
  upsertedId: null    // set if upsert: true and a new document was inserted
}
updateMany()

updateMany() applies the same update to all documents matching the filter. The syntax is identical to updateOne() — only the scope differs.

updateMany() — bulk field update

JS
// Mark all users who signed up in 2023 as legacy accounts
await users.updateMany(
  {
    createdAt: {
      $gte: new Date('2023-01-01'),
      $lt: new Date('2024-01-01'),
    },
  },
  { $set: { accountType: 'legacy', migrated: false } }
);

// Add a new field to every document in the collection
await users.updateMany(
  {},  // empty filter matches all documents
  { $set: { schemaVersion: 2 } }
);

console.log(`Modified ${result.modifiedCount} documents`);
The $set Operator

$set is the most common update operator. It sets the value of a field. If the field does not exist, $set creates it. You can update multiple fields in one operation, and you can target nested fields using dot notation.

$set — add or update fields

JS
// Set multiple top-level fields at once
await users.updateOne(
  { username: 'bob' },
  {
    $set: {
      email: 'bob@example.com',
      age: 30,
      verified: true,
    },
  }
);

// Set a nested field using dot notation
await users.updateOne(
  { username: 'bob' },
  {
    $set: {
      'address.city': 'Berlin',
      'address.country': 'Germany',
      'preferences.theme': 'dark',
    },
  }
);
The $unset Operator

$unset removes a field from a document entirely. The value you specify for the field in the operator document is irrelevant — conventionally an empty string "" is used.

$unset — remove fields

JS
// Remove the 'temporaryToken' field from a user
await users.updateOne(
  { username: 'carol' },
  { $unset: { temporaryToken: '', resetCode: '' } }
);

// Remove a nested field
await users.updateOne(
  { username: 'carol' },
  { $unset: { 'preferences.betaFeatures': '' } }
);
The $inc Operator

$inc increments a numeric field by the specified amount. Use a negative value to decrement. If the field does not exist, $inc creates it and sets it to the increment value.

$inc — increment a counter

JS
// Increment a post's view counter by 1
await db.collection('posts').updateOne(
  { slug: 'getting-started-with-mongodb' },
  { $inc: { views: 1 } }
);

// Decrement stock and increment sold count simultaneously
await db.collection('products').updateOne(
  { sku: 'WIDGET-42' },
  { $inc: { stock: -1, soldCount: 1 } }
);

// Works even if fields don't exist yet
await db.collection('stats').updateOne(
  { userId: 'user123' },
  { $inc: { loginCount: 1 } },
  { upsert: true }
);
The $push and $pull Operators

$push appends a value to an array field. $pull removes elements from an array that match a condition. Both create the array field if it does not exist.

$push — add to array

JS
// Push a single tag onto an article's tags array
await db.collection('articles').updateOne(
  { slug: 'intro-to-mongodb' },
  { $push: { tags: 'database' } }
);

// Push multiple items at once using $each
await db.collection('articles').updateOne(
  { slug: 'intro-to-mongodb' },
  { $push: { tags: { $each: ['nosql', 'tutorial', 'beginner'] } } }
);

// Push and cap the array at 10 items (keeps the last 10)
await db.collection('users').updateOne(
  { _id: userId },
  {
    $push: {
      recentlyViewed: {
        $each: [{ productId: 'abc', viewedAt: new Date() }],
        $slice: -10,  // keep only the last 10 elements
      },
    },
  }
);

$pull — remove from array

JS
// Remove a specific value from the tags array
await db.collection('articles').updateOne(
  { slug: 'intro-to-mongodb' },
  { $pull: { tags: 'draft' } }
);

// Pull elements matching a condition (remove expired items)
await db.collection('users').updateOne(
  { _id: userId },
  {
    $pull: {
      notifications: { expiresAt: { $lt: new Date() } },
    },
  }
);

// Pull from all matching documents
await db.collection('products').updateMany(
  {},
  { $pull: { categories: 'discontinued' } }
);
The $addToSet Operator

$addToSet adds a value to an array only if it is not already present — preventing duplicates. It is ideal for maintaining unique sets of values like tags or role lists.

$addToSet — unique array items

JS
// Add a role only if the user doesn't already have it
await users.updateOne(
  { username: 'dave' },
  { $addToSet: { roles: 'editor' } }
);
// Running this twice still results in roles containing 'editor' only once

// Add multiple unique values at once
await users.updateOne(
  { username: 'dave' },
  { $addToSet: { roles: { $each: ['editor', 'reviewer'] } } }
);
The $rename Operator

$rename renames a field. The document key is the current field name; the value is the new name. If the field does not exist, $rename does nothing.

JS
// Rename 'fname' to 'firstName' across all users
await users.updateMany(
  { fname: { $exists: true } },
  { $rename: { fname: 'firstName', lname: 'lastName' } }
);
The $mul Operator

$mul multiplies the value of a field by the specified number. Useful for applying percentage price changes or scaling values.

JS
// Apply a 10% price increase to all premium products
await db.collection('products').updateMany(
  { tier: 'premium' },
  { $mul: { price: 1.1 } }
);

// If the field doesn't exist, $mul sets it to 0
await db.collection('items').updateOne(
  { _id: itemId },
  { $mul: { discountFactor: 0.9 } }
);
Upsert — Insert if Not Found

Pass { upsert: true } as the third argument to insert a document if no match is found. The inserted document is built from the filter fields merged with the update operators.

Upsert example

JS
// Create or update user preferences — insert if doesn't exist
const result = await db.collection('preferences').updateOne(
  { userId: 'user123' },
  {
    $set: {
      theme: 'dark',
      language: 'en',
      updatedAt: new Date(),
    },
    $setOnInsert: {
      createdAt: new Date(),  // only set when inserting
    },
  },
  { upsert: true }
);

if (result.upsertedId) {
  console.log('Inserted new preferences doc:', result.upsertedId);
} else {
  console.log('Updated existing preferences');
}
replaceOne()

replaceOne() replaces the entire document matched by the filter with a new document. Unlike update operators, you pass a plain document as the second argument — not an operator expression.

replaceOne() — full document replacement

JS
// Replace the entire document (only _id is preserved)
await users.replaceOne(
  { username: 'eve' },
  {
    username: 'eve',
    email: 'eve@example.com',
    role: 'admin',
    createdAt: new Date(),
    // All previous fields not listed here are gone
  }
);
Warning
`replaceOne()` replaces all fields except `_id`. Use `updateOne()` with `$set` if you only want to change specific fields — `replaceOne()` will silently drop any fields you omit from the replacement document.
findOneAndUpdate()

findOneAndUpdate() atomically finds a document, updates it, and returns either the original or the updated version. It's ideal for atomic read-modify cycles such as claiming a queued job.

findOneAndUpdate() — atomic update and return

JS
// Return the document AFTER the update
const updated = await db.collection('jobs').findOneAndUpdate(
  { status: 'pending' },
  {
    $set: { status: 'processing', startedAt: new Date() },
    $inc: { attempts: 1 },
  },
  {
    returnDocument: 'after',  // 'before' returns the pre-update version
    sort: { createdAt: 1 },   // process oldest first
  }
);

if (updated) {
  console.log('Processing job:', updated._id);
} else {
  console.log('No pending jobs');
}
Array Update Operators Summary

Operator

Purpose

Example

$push

Append to array

{ $push: { tags: "new" } }

$pull

Remove matching elements

{ $pull: { tags: "old" } }

$addToSet

Append only if not present

{ $addToSet: { roles: "admin" } }

$pop

Remove first (-1) or last (1) element

{ $pop: { items: 1 } }

$pullAll

Remove all listed values

{ $pullAll: { scores: [0, -1] } }

$each

Push/addToSet multiple values

{ $push: { tags: { $each: ["a","b"] } } }

$slice

Trim array after $push (with $each)

{ $push: { log: { $each: [entry], $slice: -100 } } }

$sort

Sort array after $push (with $each)

{ $push: { scores: { $each: [9], $sort: -1 } } }

Tip
Always use update operators like `$set` rather than `replaceOne()` unless you genuinely want to replace the entire document — it prevents accidentally wiping fields you meant to keep.
Note
`updateOne()` and `updateMany()` return `matchedCount` and `modifiedCount`. `matchedCount` can be 1 while `modifiedCount` is 0 if the update didn't change anything — for example, setting a field to the value it already holds.