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
Comments