MongoDB Delete Document
Deleting is the "D" in CRUD. MongoDB removes documents with deleteOne(), deleteMany() and findOneAndDelete(), and it can also delete old documents automatically with a TTL index.
This tutorial covers each method with examples, explains the difference between deleting documents and dropping a collection, shows automatic expiry and the soft-delete pattern, and lists the safety habits that prevent painful mistakes.
1. Sample Data
use bookstore
db.orders.drop()
db.orders.insertMany([
{ _id: 1, customer: "C-1", total: 919, status: "SHIPPED", createdAt: ISODate("2026-09-28") },
{ _id: 2, customer: "C-2", total: 650, status: "CANCELLED", createdAt: ISODate("2026-10-02") },
{ _id: 3, customer: "C-1", total: 420, status: "CANCELLED", createdAt: ISODate("2026-10-05") },
{ _id: 4, customer: "C-3", total: 1180, status: "PENDING", createdAt: ISODate("2026-10-16") }
])
2. The Delete Methods
| Method | Deletes | Returns |
|---|---|---|
deleteOne(filter) | The first matching document | { acknowledged, deletedCount } |
deleteMany(filter) | Every matching document | { acknowledged, deletedCount } |
findOneAndDelete(filter, options) | One matching document | The deleted document itself |
3. deleteOne()
db.orders.deleteOne({ _id: 3 })
// { acknowledged: true, deletedCount: 1 }
db.orders.deleteOne({ _id: 999 })
// { acknowledged: true, deletedCount: 0 } <-- no match is not an error
If the filter matches several documents, deleteOne() removes only the first one it finds, in no guaranteed order. Use a unique field such as _id when you mean one specific document.
4. deleteMany()
// Remove all cancelled orders
db.orders.deleteMany({ status: "CANCELLED" })
// { acknowledged: true, deletedCount: 1 }
// Remove orders created before October 2026
db.orders.deleteMany({ createdAt: { $lt: ISODate("2026-10-01") } })
// Remove EVERY document but keep the collection and its indexes
db.orders.deleteMany({})
deleteMany({}) empties the whole collection. Always run db.orders.countDocuments(filter) or find(filter) with the same filter first, and check the number before deleting.5. findOneAndDelete()
Use findOneAndDelete() when you need the removed document, for example to process the next message from a simple queue:
db.emailQueue.insertMany([
{ to: "a@example.com", priority: 1, createdAt: new Date() },
{ to: "b@example.com", priority: 5, createdAt: new Date() }
])
const job = db.emailQueue.findOneAndDelete(
{},
{ sort: { priority: -1, createdAt: 1 }, projection: { to: 1 } }
)
// { _id: ObjectId('...'), to: 'b@example.com' }
The find and the delete happen atomically, so two workers can never receive the same document.
6. Deleting All Documents vs Dropping the Collection
deleteMany({}) | drop() | |
|---|---|---|
| Collection remains | Yes, empty | No |
| Indexes and validation rules | Kept | Removed |
| Speed on large collections | Slow, document by document | Very fast |
| Replication cost | One oplog entry per document | A single oplog entry |
See Drop Collection for details.
7. Automatic Deletion with TTL Indexes
A TTL (time to live) index on a date field tells MongoDB to delete documents automatically once they are older than a given number of seconds. It is perfect for sessions, verification codes, temporary carts and logs.
// Delete login sessions 30 minutes after lastSeen
db.sessions.createIndex({ lastSeen: 1 }, { expireAfterSeconds: 1800 })
// Expire each document at its own time: set expireAfterSeconds to 0
db.otpCodes.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 })
db.otpCodes.insertOne({ user: "C-1", code: "482913", expiresAt: new Date(Date.now() + 5 * 60 * 1000) })
- A background task removes expired documents about once a minute, so deletion is not to the second.
- The field must hold a
Date(or an array of dates); documents without it, or with a non-date value, never expire. - Change the expiry later with
collMod, without rebuilding the index.
expireAfterSeconds does the same job with better compression.8. The Soft-Delete Pattern
Sometimes you must be able to undo a delete, or keep a record for auditing. Instead of removing the document, mark it:
// "Delete"
db.customers.updateOne({ _id: "C-2" }, { $set: { deletedAt: new Date() } })
// Normal queries ignore deleted customers
db.customers.find({ deletedAt: { $exists: false } })
// Restore
db.customers.updateOne({ _id: "C-2" }, { $unset: { deletedAt: "" } })
// Optionally purge for real after 90 days, with a TTL index
db.customers.createIndex({ deletedAt: 1 }, { expireAfterSeconds: 60 * 60 * 24 * 90 })
A partial index that only covers non-deleted documents keeps everyday queries fast:
db.customers.createIndex({ email: 1 }, { partialFilterExpression: { deletedAt: { $exists: false } } })
9. About remove()
Older tutorials use db.collection.remove(). It is deprecated in mongosh; use deleteOne() or deleteMany(), which make it explicit how many documents you intend to delete.
10. Deleting from Java
import com.mongodb.client.*;
import com.mongodb.client.model.FindOneAndDeleteOptions;
import com.mongodb.client.result.DeleteResult;
import org.bson.Document;
import java.time.Instant;
import java.util.Date;
import static com.mongodb.client.model.Filters.*;
import static com.mongodb.client.model.Sorts.*;
public class DeleteOrdersExample {
public static void main(String[] args) {
try (MongoClient client = MongoClients.create("mongodb://localhost:27017")) {
MongoDatabase db = client.getDatabase("bookstore");
MongoCollection<Document> orders = db.getCollection("orders");
DeleteResult one = orders.deleteOne(eq("_id", 3));
DeleteResult many = orders.deleteMany(and(eq("status", "CANCELLED"),
lt("createdAt", Date.from(Instant.parse("2026-10-01T00:00:00Z")))));
System.out.println(one.getDeletedCount() + " + " + many.getDeletedCount() + " deleted");
Document next = db.getCollection("emailQueue").findOneAndDelete(
new Document(), new FindOneAndDeleteOptions().sort(orderBy(descending("priority"), ascending("createdAt"))));
}
}
}
11. Safety Checklist
- Preview with
find(filter)orcountDocuments(filter)before anydeleteMany(). - Never build a delete filter from unchecked user input; an empty or missing value can turn into
{}. - Use write concern
majorityfor important deletes, and keep tested backups. - For large clean-ups, delete in batches (for example by date range) to limit load and replication lag.
- Prefer TTL indexes over scheduled delete jobs for data that simply expires.
12. Summary
deleteOne()removes the first match,deleteMany()removes all matches, andfindOneAndDelete()removes one document and returns it.- A delete that matches nothing returns
deletedCount: 0, not an error. deleteMany({})empties a collection but keeps it;drop()removes it entirely and much faster.- TTL indexes delete expired documents automatically; soft deletes keep data recoverable.