Skip to main content
โšก Calmops

MongoDB Delete Commands: A Complete Guide

Introduction

MongoDB provides several ways to delete documents and collections. Choosing the right method depends on whether you need to remove one document, many documents matching a filter, or an entire collection. This guide covers all the delete operations with practical examples.

deleteOne: Remove a Single Document

deleteOne() removes the first document that matches the filter:

// Delete the first document where status is "inactive"
db.users.deleteOne({ status: "inactive" })

// Delete by _id (most common pattern)
db.users.deleteOne({ _id: ObjectId("507f1f77bcf86cd799439011") })

// Delete with a compound filter
db.orders.deleteOne({
  userId: ObjectId("507f1f77bcf86cd799439011"),
  status: "cancelled"
})

The result object tells you how many documents were deleted:

{ acknowledged: true, deletedCount: 1 }
// or
{ acknowledged: true, deletedCount: 0 }  // no match found

deleteMany: Remove Multiple Documents

deleteMany() removes all documents matching the filter:

// Delete all inactive users
db.users.deleteMany({ status: "inactive" })

// Delete all documents older than 30 days
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
db.logs.deleteMany({ createdAt: { $lt: thirtyDaysAgo } })

// Delete all documents in a collection (but keep the collection)
db.tempData.deleteMany({})

// Delete with opt-out flag
db.mailing.deleteMany({ "opt-out": true })

drop: Remove an Entire Collection

drop() removes the entire collection including all its documents and indexes:

// Drop the entire collection
db.tempData.drop()

// Returns true if successful, false if collection didn't exist

When to use drop() vs deleteMany({}):

Method Removes documents Removes indexes Removes collection Speed
deleteMany({}) Yes No No Slower
drop() Yes Yes Yes Faster

Use drop() when you want to completely remove a collection and start fresh. Use deleteMany({}) when you want to clear documents but keep the collection structure and indexes.

findOneAndDelete: Delete and Return the Document

findOneAndDelete() atomically finds, deletes, and returns the deleted document:

// Delete and get the deleted document back
const deleted = db.jobs.findOneAndDelete(
  { status: "pending" },
  { sort: { createdAt: 1 } }  // delete the oldest pending job
)

console.log(deleted)
// => { _id: ..., status: "pending", createdAt: ..., ... }

This is useful for implementing work queues โ€” atomically claim and remove a job.

Bulk Delete Operations

For deleting large numbers of documents efficiently, use bulk operations:

// Bulk delete with bulkWrite
db.orders.bulkWrite([
  { deleteOne: { filter: { _id: ObjectId("...") } } },
  { deleteOne: { filter: { _id: ObjectId("...") } } },
  { deleteMany: { filter: { status: "expired" } } }
])

Conditional Deletes with Query Operators

// Delete documents where score is less than 50
db.scores.deleteMany({ score: { $lt: 50 } })

// Delete documents where tags array contains "spam"
db.posts.deleteMany({ tags: "spam" })

// Delete documents where field exists
db.users.deleteMany({ tempToken: { $exists: true } })

// Delete documents matching multiple conditions
db.sessions.deleteMany({
  $and: [
    { expiresAt: { $lt: new Date() } },
    { active: false }
  ]
})

// Delete documents where field is in a list
db.products.deleteMany({
  sku: { $in: ["SKU001", "SKU002", "SKU003"] }
})

Safe Delete Patterns

Always Test Your Filter First

Before deleting, run a find() with the same filter to verify what will be deleted:

// Step 1: preview what will be deleted
db.users.find({ status: "inactive", lastLogin: { $lt: new Date("2024-01-01") } })

// Step 2: if the results look right, delete
db.users.deleteMany({ status: "inactive", lastLogin: { $lt: new Date("2024-01-01") } })

Soft Delete Pattern

Instead of physically deleting documents, mark them as deleted. This preserves data for auditing and recovery:

// Soft delete โ€” mark as deleted instead of removing
db.users.updateOne(
  { _id: userId },
  {
    $set: {
      deleted: true,
      deletedAt: new Date(),
      deletedBy: currentUserId
    }
  }
)

// Query active users (exclude soft-deleted)
db.users.find({ deleted: { $ne: true } })

// Create a partial index to efficiently query non-deleted documents
db.users.createIndex(
  { email: 1 },
  { partialFilterExpression: { deleted: { $ne: true } } }
)

Backup Before Bulk Delete

// Copy documents to an archive collection before deleting
const toDelete = db.orders.find({ status: "cancelled", createdAt: { $lt: cutoffDate } }).toArray()
if (toDelete.length > 0) {
  db.orders_archive.insertMany(toDelete)
  db.orders.deleteMany({ status: "cancelled", createdAt: { $lt: cutoffDate } })
}

Transactions (Multi-Document Delete)

For operations that must be atomic across multiple collections, use transactions:

const session = client.startSession()

try {
  session.startTransaction()

  await db.orders.deleteOne({ _id: orderId }, { session })
  await db.orderItems.deleteMany({ orderId: orderId }, { session })
  await db.payments.deleteMany({ orderId: orderId }, { session })

  await session.commitTransaction()
  console.log("Order and related data deleted successfully")
} catch (error) {
  await session.abortTransaction()
  console.error("Delete failed, rolled back:", error)
} finally {
  session.endSession()
}

Performance Considerations

Use Indexes on Filter Fields

Deletes without an index cause a full collection scan:

// Ensure the field you filter on is indexed
db.logs.createIndex({ createdAt: 1 })

// Now this delete is fast
db.logs.deleteMany({ createdAt: { $lt: cutoffDate } })

Delete in Batches for Large Collections

Deleting millions of documents at once can lock the collection and impact performance:

// Delete in batches of 1000
async function batchDelete(collection, filter, batchSize = 1000) {
  let totalDeleted = 0
  let deleted

  do {
    const docs = await collection.find(filter).limit(batchSize).toArray()
    if (docs.length === 0) break

    const ids = docs.map(d => d._id)
    const result = await collection.deleteMany({ _id: { $in: ids } })
    deleted = result.deletedCount
    totalDeleted += deleted

    console.log(`Deleted ${totalDeleted} documents so far...`)
    await new Promise(resolve => setTimeout(resolve, 100)) // small pause
  } while (deleted === batchSize)

  return totalDeleted
}

Quick Reference

// Delete one document
db.collection.deleteOne({ field: value })

// Delete many documents
db.collection.deleteMany({ field: value })

// Delete all documents (keep collection)
db.collection.deleteMany({})

// Drop entire collection
db.collection.drop()

// Delete and return the document
db.collection.findOneAndDelete({ field: value })

// Legacy remove() โ€” still works but deprecated
db.collection.remove({ field: value })        // removes all matches
db.collection.remove({ field: value }, true)  // removes only first match

Resources

Comments