Delete
MongoDB gives you deleteOne() and deleteMany() to remove documents matching a filter, plus drop() to remove an entire collection in one shot. Deletes are permanent — there's no undo — so filter precision and, where it matters, soft deletes deserve real attention.
deleteOne()
Removes the first document matching the filter. If the filter matches several documents, only one is removed — which one is not something you should rely on unless the filter is unique.
deleteOne basics
const result = db.users.deleteOne({ email: "alice@example.com" })
result.deletedCount // 0 or 1
result.acknowledged // truedeleteMany()
Removing every matching document
const result = db.sessions.deleteMany({ expiresAt: { $lt: new Date() } })
result.deletedCount // number of expired sessions removeddb.collection.deleteMany({}) — removes every document in the collection. This is a common copy-paste accident. Always double-check the filter with a find() or countDocuments() using the exact same filter before running the delete.Preview before you delete
// Step 1 — see exactly what would be removed
db.sessions.countDocuments({ expiresAt: { $lt: new Date() } })
db.sessions.find({ expiresAt: { $lt: new Date() } }).limit(5)
// Step 2 — only then run the delete with the identical filter
db.sessions.deleteMany({ expiresAt: { $lt: new Date() } })Deleting All Documents vs Dropping the Collection
deleteMany({}) | collection.drop() | |
|---|---|---|
Removes | All documents | The entire collection, including its documents AND indexes |
Indexes | Preserved | Dropped along with the collection |
Speed on huge collections | Slower — deletes are logged per document | Very fast — a metadata-level operation |
Collection still exists after? | Yes, empty | No — must be recreated (implicitly, on next insert) if needed |
drop() removes the collection entirely
db.tempImport.drop() // true if it existed and was dropped // The collection, its documents, and ALL of its indexes are gone. // A subsequent insert recreates the collection with NO indexes except the default _id one.
deleteMany({}) is usually simpler than drop() + re-creating every index by hand. If you don't care about the existing indexes, drop() is dramatically faster on large collections.Soft-Delete Pattern
Many applications never physically delete records that matter for auditing, analytics, or recovery — instead they flag documents as deleted and filter them out of normal queries.
Soft delete via a flag + timestamp
// "Delete" — really just flags the document
db.orders.updateOne(
{ _id: 42 },
{ $set: { deletedAt: new Date(), status: "deleted" } }
)
// Normal application queries always exclude soft-deleted documents
db.orders.find({ deletedAt: { $exists: false } })
// A partial index keeps the "active documents" query fast without
// indexing the (usually much smaller) deleted set
db.orders.createIndex(
{ customerId: 1 },
{ partialFilterExpression: { deletedAt: { $exists: false } } }
)deletedAt) that permanently purges soft-deleted documents after a retention window, so the "deleted" set doesn't grow forever.TTL-Based Deletion
For data that should expire automatically — sessions, verification tokens, cached results — a TTL (time-to-live) index lets MongoDB's background process delete documents for you once a date field passes a threshold, with no application code required. See the dedicated TTL index page for the full mechanics.
TTL index — auto-expiring documents
// Documents are removed ~60 seconds after "createdAt"
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 60 })Write Concern for Deletes
Just like inserts and updates, delete operations accept a writeConcern option controlling how many replica set members must acknowledge the delete before the driver treats it as durable.
Write concern on a delete
db.orders.deleteOne(
{ _id: 42 },
{ writeConcern: { w: "majority", wtimeout: 5000 } }
)w: "majority" and prefer soft deletes over physical deletes in the first place.Example Session
test> db.sessions.countDocuments({ expiresAt: { $lt: new Date() } })
14
test> db.sessions.deleteMany({ expiresAt: { $lt: new Date() } })
{ acknowledged: true, deletedCount: 14 }
test> db.tempImport.drop()
true
test> show collections
orders
products
sessions
usersdeleteOne()— removes at most one matching document.deleteMany()— removes every matching document; an empty filter deletes everything.drop()— removes the entire collection AND its indexes; much faster for clearing large collections.Soft delete (a
deletedAtflag) when you need auditability or recovery.TTL index when documents should expire automatically after a fixed time window.