00:00

MongoDB Delete Document

Last updated:

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

mongosh
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

MethodDeletesReturns
deleteOne(filter)The first matching document{ acknowledged, deletedCount }
deleteMany(filter)Every matching document{ acknowledged, deletedCount }
findOneAndDelete(filter, options)One matching documentThe deleted document itself

3. deleteOne()

mongosh
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()

mongosh
// 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:

mongosh
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 remainsYes, emptyNo
Indexes and validation rulesKeptRemoved
Speed on large collectionsSlow, document by documentVery fast
Replication costOne oplog entry per documentA 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.

mongosh
// 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.
For measurements over time, a time series collection with 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:

mongosh
// "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:

mongosh
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

DeleteOrdersExample.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) or countDocuments(filter) before any deleteMany().
  • Never build a delete filter from unchecked user input; an empty or missing value can turn into {}.
  • Use write concern majority for 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, and findOneAndDelete() 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.