updateMany() - Update documents in batches

Syntax

collection(collectionName).updateMany(filter, update, options)

Parameters

filter (Object, required)

Filter criteria to match the documents to be updated.

{ status: "inactive" }
{ age: { $gte: 18, $lt: 65 }, role: "user" }

update (Object, required)

For update operations, the update operator must be used.

{ $set: { status: "active", updatedAt: new Date() } }
{ $inc: { views: 1 } }

options (Object, optional)

OptionsTypeDefaultDescription
upsertBooleanfalseWhether to insert one new document when no documents match the filter
writeConcernObject-Write follow options
bypassDocumentValidationBooleanfalseWhether to bypass document verification
commentString-Operation comments
collationObject-Sorting Rules
arrayFiltersArray-Array filter
hintString/Object-Index Tips

Return value

Return Promise<UpdateResult>:

{
  acknowledged: true,
  matchedCount: 10,      //Number of matching documents
  modifiedCount: 10,     //The actual number of documents modified
  upsertedId: null,
  upsertedCount: 0
}

Upsert semantics

updateMany(filter, update, { upsert: true }) follows MongoDB's native semantics:

  • If one or more documents match filter, all matching documents are updated.
  • If no documents match filter, MongoDB inserts one new document derived from the equality parts of filter and the update document.
  • It is not a per-input bulk upsert. For multiple independent keys, run separate upsertOne() calls or use native bulkWrite updateOne models with upsert: true.

Example

Batch update status

//Activate all inactive users
const result = await collection("users").updateMany(
  { status: "inactive" },
  { $set: { status: "active", updatedAt: new Date() } }
);

console.log("Updated:", result.modifiedCount, "users");

Batch increment

//All product views +10
const result = await collection("products").updateMany(
  {},
  { $inc: { views: 10 } }
);

console.log("Updated:", result.modifiedCount, "products");

Conditional batch update

//Mark large orders as high priority
const result = await collection("orders").updateMany(
  {
    status: "pending",
    amount: { $gte: 1000 }
  },
  { $set: { priority: "high" } }
);

Add fields in batches

//Add new fields to all articles
await collection("articles").updateMany(
  {},
  {
    $set: {
      published: true,
      publishedAt: new Date(),
      version: 1
    }
  }
);

Delete fields in batches

//Clean temporary fields for all documents
await collection("documents").updateMany(
  {},
  { $unset: { tempField: "", debugMode: "" } }
);

Use array filter

//Update subject grades with grades >= 80
await collection("students").updateMany(
  { studentId: { $exists: true } },
  { $set: { "scores.$[elem].grade": "A" } },
  {
    arrayFilters: [{ "elem.score": { $gte: 80 } }]
  }
);

Complex updates with multiple conditions

await collection("users").updateMany(
  {
    role: "user",
    age: { $gte: 18, $lt: 65 },
    status: "active"
  },
  {
    $set: { category: "adult", verifiedAt: new Date() },
    $inc: { loginBonus: 10 }
  }
);

Batch update nested fields

//Add default address for all users
await collection("users").updateMany(
  {},
  {
    $set: {
      "profile.address.country": "China",
      "profile.verified": true
    }
  }
);

Performance optimization

1. Use index optimization filtering

//Make sure filter fields are indexed
await collection("users").updateMany(
  { status: "inactive" }, //The status field should have an index
  { $set: { status: "active" } }
);

2. Process large-scale updates in batches

//❌ Not recommended - update millions of documents at once
await collection("users").updateMany(
  {},
  { $set: { migrated: true } }
);

//✅ Recommended - batch processing
let lastId = null;
const batchSize = 10000;

while (true) {
  const filter = lastId
    ? { _id: { $gt: lastId } }
    : {};

  const result = await collection("users")
    .find(filter)
    .limit(batchSize)
    .toArray();

  if (result.length === 0) break;

  const ids = result.map(doc => doc._id);
  await collection("users").updateMany(
    { _id: { $in: ids } },
    { $set: { migrated: true } }
  );

  lastId = result[result.length - 1]._id;
  console.log(`Processed ${batchSize} documents`);
}

3. Performance test example

//Mass update performance test
const startTime = Date.now();

const result = await collection("logs").updateMany(
  { processed: false },
  {
    $set: { processed: true, processedAt: new Date() }
  }
);

const duration = Date.now() - startTime;
console.log(`Updated ${result.modifiedCount} documents in ${duration}ms`);
console.log(`Speed: ${Math.round(result.modifiedCount / duration * 1000)} docs/sec`);

Differences from updateOne

FeaturesupdateOneupdateMany
UPDATE NUMBERFirst match onlyAll matches
PerformanceFast (single write)Slow (batch write)
Usage ScenariosUpdate a single documentBatch update
Return valueCounting informationCounting information
//updateOne - update only the first one
await collection("users").updateOne(
  { status: "inactive" },
  { $set: { status: "active" } }
);
//Result: modifiedCount = 1

//updateMany - updates all matches
await collection("users").updateMany(
  { status: "inactive" },
  { $set: { status: "active" } }
);
//Result: modifiedCount = N (number of all matches)

Common scenarios

Scenario 1: Batch activation of users

const result = await collection("users").updateMany(
  {
    status: "pending",
    emailVerified: true,
    createdAt: { $lt: new Date(Date.now() - 24 * 60 * 60 * 1000) }
  },
  {
    $set: {
      status: "active",
      activatedAt: new Date()
    }
  }
);

console.log(`Activated ${result.modifiedCount} users`);

Scenario 2: Mark expired data in batches

const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);

await collection("sessions").updateMany(
  {
    lastAccessAt: { $lt: thirtyDaysAgo },
    expired: { $ne: true }
  },
  {
    $set: {
      expired: true,
      expiredAt: new Date()
    }
  }
);

Scenario 3: Bulk price adjustment

//10% off on all products in selected categories
await collection("products").updateMany(
  { category: "electronics", onSale: false },
  {
    $mul: { price: 0.9 },
    $set: { onSale: true, saleStartAt: new Date() }
  }
);

Scenario 4: Batch data migration

//Migrate old fields to new fields
await collection("users").updateMany(
  { oldField: { $exists: true } },
  [
    {
      $set: {
        newField: "$oldField"
      }
    },
    {
      $unset: "oldField"
    }
  ]
);

Error handling

try {
  const result = await collection("users").updateMany(
    { status: "inactive" },
    { $set: { status: "active" } }
  );

  if (result.matchedCount === 0) {
    console.log("No matching document found");
  } else {
    console.log(`Successfully updated ${result.modifiedCount}/${result.matchedCount} documents`);
  }
} catch (err) {
  if (err.code === "INVALID_ARGUMENT") {
    console.error("Parameter error:", err.message);
  } else if (err.code === "WRITE_ERROR") {
    console.error("Batch write error:", err.message);
  } else {
    console.error("Unknown error:", err);
  }
}

Caching behavior

updateMany does not clear query caches by default after a successful modification. Use cache.invalidate or autoInvalidate: true when the write should clear cache:

//Caching query results
await collection("users").find({ status: "inactive" }, { cache: 5000 });

//Batch update and clear cache when needed
await collection("users").updateMany(
  { status: "inactive" },
  { $set: { status: "active" } }
);
//cache cleared

//The next query will get it from the database

Note: Explicit invalidation runs only when the write path decides the operation changed data.

Slow query log

If the batch update operation takes a long time, slow query logs will be automatically recorded:

//Large batch updates may trigger slow query logs
await collection("logs").updateMany(
  { processed: false },
  { $set: { processed: true } }
);
//If it takes > 1000ms (default threshold), a log will be logged:
//[updateMany] Slow operation warning { ns: 'db.logs', duration: 1520, matchedCount: 50000, ... }

Best Practices

1. Verify update results

const result = await collection("users").updateMany(
  { status: "inactive" },
  { $set: { status: "active" } }
);

if (result.matchedCount !== result.modifiedCount) {
  console.warn(
    `Some documents are not modified: ${result.matchedCount - result.modifiedCount} document values are already target values`
  );
}

2. Add operation comments

await collection("users").updateMany(
  { status: "inactive" },
  { $set: { status: "active" } },
  { comment: "Batch activation of users - Operation activity 202511" }
);

3. Use transaction processing key batch update

const session = client.startSession();
try {
  await session.withTransaction(async () => {
    await collection("users").updateMany(
      { status: "pending" },
      { $set: { status: "active" } },
      { session }
    );

    await collection("audit_logs").insertOne(
      {
        action: "batch_activate",
        timestamp: new Date(),
        count: result.modifiedCount
      },
      { session }
    );
  });
} finally {
  await session.endSession();
}

4. Monitor update progress

let totalUpdated = 0;
const batchSize = 1000;

while (true) {
  const result = await collection("users").updateMany(
    {
      status: "inactive",
      updated: { $ne: true }
    },
    {
      $set: { status: "active", updated: true }
    }
  );

  totalUpdated += result.modifiedCount;

  if (result.matchedCount < batchSize) {
    break;
  }

  console.log(`Progress: ${totalUpdated} documents updated`);
  await new Promise(resolve => setTimeout(resolve, 100)); //avoid overload
}

console.log(`Completed: ${totalUpdated} documents updated`);

References