MongoDBDelete Documents

Delete Documents

MongoDB provides deleteOne() and deleteMany() for removing documents. Always use a precise filter — MongoDB will happily delete more than you intend.

deleteOne()

deleteOne() removes the first document that matches the filter. If multiple documents match, only one is deleted.

deleteOne() — remove a single document

JS
const { MongoClient, ObjectId } = require('mongodb');
const client = new MongoClient('mongodb://localhost:27017');
const db = client.db('mydb');
const users = db.collection('users');

// Delete by _id (most precise)
await users.deleteOne({
  _id: new ObjectId('64a1f2c3d4e5f6a7b8c9d0e1'),
});

// Delete by a unique field
await users.deleteOne({ email: 'test@example.com' });

// Delete the oldest inactive session
await db.collection('sessions').deleteOne(
  { active: false },
  // Without a sort, which document is deleted is non-deterministic
);

deleteOne() result

JS
{ acknowledged: true, deletedCount: 1 }

// If no document matched the filter:
{ acknowledged: true, deletedCount: 0 }
deleteMany()

deleteMany() removes all documents matching the filter. It returns the count of deleted documents.

deleteMany() — remove multiple documents

JS
// Delete all inactive users
const result = await users.deleteMany({ active: false });
console.log(`Deleted ${result.deletedCount} inactive users`);

// Delete records older than 90 days
const cutoff = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);
await db.collection('auditLogs').deleteMany({
  createdAt: { $lt: cutoff },
});

// Delete all documents with a specific tag
await db.collection('articles').deleteMany({
  tags: 'draft',
});
Warning
`db.collection.deleteMany({})` deletes **ALL** documents in a collection. Always double-check your filter before running `deleteMany`. Consider running a `find()` with the same filter first to preview what will be removed.
findOneAndDelete()

findOneAndDelete() atomically finds and removes a document, returning the deleted document. This is useful when you need the document's content after deletion — for example, implementing a work queue where you pop and process a job in one step.

findOneAndDelete() — return deleted document

JS
// Pop the highest-priority pending job from a queue
const job = await db.collection('jobs').findOneAndDelete(
  { status: 'pending' },
  { sort: { priority: -1, createdAt: 1 } }
);

if (job) {
  console.log('Processing job:', job._id, job.type);
  await processJob(job);
} else {
  console.log('Queue is empty');
}

// Delete and inspect a one-time verification token
const token = await db.collection('tokens').findOneAndDelete({
  value: req.params.token,
  expiresAt: { $gt: new Date() },
});

if (!token) {
  throw new Error('Invalid or expired token');
}
Soft Delete Pattern

In production, hard deletes are often replaced with soft deletes — adding a deletedAt timestamp field instead of removing the document. This preserves audit history, supports undo operations, and prevents accidental data loss.

Soft delete implementation

JS
// Soft-delete a user (mark as deleted instead of removing)
await users.updateOne(
  { _id: userId },
  {
    $set: {
      deletedAt: new Date(),
      deletedBy: currentUser._id,
    },
  }
);

// Query only active (non-deleted) users
const activeUsers = await users
  .find({ deletedAt: { $exists: false } })
  .toArray();

// Restore a soft-deleted user
await users.updateOne(
  { _id: userId },
  { $unset: { deletedAt: '', deletedBy: '' } }
);

// Create a partial index to keep queries fast
await users.createIndex(
  { email: 1 },
  {
    unique: true,
    partialFilterExpression: { deletedAt: { $exists: false } },
  }
);
Dropping Collections and Databases

When you need to remove an entire collection or database, use drop() or dropDatabase(). These operations are instant and bypass document-level operations entirely.

Drop operations

JS
// Drop an entire collection (removes all documents AND indexes)
await db.collection('tempImports').drop();

// Drop the entire database
await db.dropDatabase();

// Safe pattern: check existence before dropping
const collections = await db.listCollections({ name: 'tempImports' }).toArray();
if (collections.length > 0) {
  await db.collection('tempImports').drop();
}
Warning
`drop()` is instant and irreversible without a backup. It removes all documents **and** all indexes on the collection. There is no confirmation prompt — one misplaced call wipes the collection permanently.
TTL Indexes for Automatic Deletion

For time-based data such as sessions, logs, and caches, use a TTL (Time-To-Live) index to auto-expire documents. MongoDB's background thread checks TTL indexes every 60 seconds and removes expired documents automatically — no application-level cleanup needed.

TTL index — auto-delete after 1 hour

JS
// Create a TTL index on the 'createdAt' field
// Documents are deleted 3600 seconds (1 hour) after createdAt
await db.collection('sessions').createIndex(
  { createdAt: 1 },
  { expireAfterSeconds: 3600 }
);

// Insert a session — it will auto-expire in 1 hour
await db.collection('sessions').insertOne({
  userId: 'user123',
  token: 'abc...',
  createdAt: new Date(),  // TTL is calculated from this field
});

// TTL index for log retention (delete after 30 days)
await db.collection('requestLogs').createIndex(
  { timestamp: 1 },
  { expireAfterSeconds: 30 * 24 * 60 * 60 }
);
Bulk Delete with bulkWrite()

bulkWrite() lets you combine multiple delete (and other write) operations into a single round-trip to the server. This is more efficient than issuing many individual operations.

Bulk delete operations

JS
const result = await users.bulkWrite([
  // Delete a specific user by ID
  {
    deleteOne: {
      filter: { _id: new ObjectId('64a1f2c3d4e5f6a7b8c9d0e1') },
    },
  },
  // Delete all unverified users older than 7 days
  {
    deleteMany: {
      filter: {
        verified: false,
        createdAt: {
          $lt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
        },
      },
    },
  },
]);

console.log('Deleted:', result.deletedCount);

Method

Returns

Use Case

deleteOne()

{ deletedCount }

Remove a single known document

deleteMany()

{ deletedCount }

Bulk removal by condition

findOneAndDelete()

The deleted document

Pop-and-process patterns, queue consumers

drop() (collection)

void

Wipe an entire collection including indexes

dropDatabase()

void

Remove an entire database and all its collections

Tip
Test your delete filter with `find()` first — verify it matches exactly what you intend to delete, then replace `find()` with `deleteMany()`. This one habit prevents accidental mass deletions.
Note
Deleted documents cannot be recovered unless you have a backup or have enabled Point-in-Time Recovery on MongoDB Atlas. For critical data, consider the soft delete pattern or enabling Atlas continuous backups before running bulk deletions.