Fix MongoDB 'ns not found' Error When Calling drop() or dropIndex()

beginner๐Ÿƒ MongoDB2026-06-20| MongoDB 4.xโ€“7.x, Node.js (Mongoose/MongoDB Node Driver), Python (PyMongo), MongoDB Shell (mongosh / legacy mongo)

Error Message

MongoError: ns not found
#mongodb#drop#dropIndex#collection#namespace

The Error

You call db.myCollection.drop() or db.myCollection.dropIndex('some_index') and MongoDB throws back:

MongoError: ns not found

Newer drivers and mongosh wrap it slightly differently:

MongoServerError: ns not found
MongoError: ns not found (codeName: 'NamespaceNotFound', code: 26)

The operation stops dead โ€” no partial cleanup, no fallback.

Root Cause

The namespace you tried to drop doesn't exist. That's it. MongoDB doesn't silently skip missing namespaces the way SQL's DROP TABLE IF EXISTS does โ€” unless you explicitly tell it to.

A few situations reliably trigger this:

  • Running a migration or seed script twice โ€” the second run tries to drop what the first already removed.
  • Test teardown dropping a collection that never got created because setup failed partway through.
  • A typo in the collection name โ€” user instead of users, for example.
  • Calling dropIndex() after a previous script already removed that index.
  • Dropping a collection from a freshly wiped database in CI.

Fix

1. MongoDB Shell โ€” Check Before Dropping

The safest mongosh approach: verify the collection exists before touching it.

if (db.getCollectionNames().includes('myCollection')) {
  db.myCollection.drop();
  print('Dropped.');
} else {
  print('Collection does not exist, skipping.');
}

Same idea for indexes โ€” pull the current index list first:

const indexes = db.myCollection.getIndexes().map(i => i.name);
if (indexes.includes('email_1')) {
  db.myCollection.dropIndex('email_1');
}

2. Node.js โ€” MongoDB Native Driver

Rather than checking upfront, catch error code 26 (NamespaceNotFound) and swallow it. Everything else still throws.

const { MongoClient } = require('mongodb');

async function safeDrop(db, collectionName) {
  try {
    await db.collection(collectionName).drop();
    console.log(`Dropped ${collectionName}`);
  } catch (err) {
    if (err.code === 26) {
      console.log(`${collectionName} doesn't exist, skipping drop.`);
    } else {
      throw err;
    }
  }
}

const client = new MongoClient(process.env.MONGO_URI);
await client.connect();
const db = client.db('mydb');
await safeDrop(db, 'oldCollection');

For dropIndex, you need to handle two codes: 26 when the collection itself is missing, and 27 (IndexNotFound) when the collection exists but the index is already gone:

async function safeDropIndex(collection, indexName) {
  try {
    await collection.dropIndex(indexName);
  } catch (err) {
    if (err.code === 26 || err.code === 27) {
      return; // already gone
    }
    throw err;
  }
}

3. Mongoose

Mongoose wraps the native driver, so the same error code applies. The catch block also guards against older driver versions that surface the error as a message string instead:

async function safeDrop(modelOrCollectionName) {
  try {
    await mongoose.connection.dropCollection(modelOrCollectionName);
  } catch (err) {
    if (err.code === 26 || err.message === 'ns not found') {
      return;
    }
    throw err;
  }
}

// In test teardown
afterEach(async () => {
  await safeDrop('users');
});

4. PyMongo (Python)

PyMongo's drop_collection() quietly eats the ns not found error internally in most versions. drop_index() does not โ€” you have to catch it yourself.

from pymongo import MongoClient
from pymongo.errors import OperationFailure

client = MongoClient("mongodb://localhost:27017")
db = client["mydb"]

# drop_collection is usually safe, but being explicit never hurts
try:
    db.drop_collection("old_collection")
except OperationFailure as e:
    if e.code == 26:
        print("Collection doesn't exist, skipping.")
    else:
        raise

# drop_index requires manual guarding
try:
    db.my_collection.drop_index("email_1")
except OperationFailure as e:
    if e.code in (26, 27):  # 26 = collection missing, 27 = index missing
        pass
    else:
        raise

5. Idempotent Migrations

CI pipelines often rerun migrations on rollback-and-retry. Wrap every destructive step so reruns don't fail at 2am:

// migration_001_drop_old_index.js
module.exports = {
  async up(db) {
    const col = db.collection('orders');
    try {
      await col.dropIndex('status_1_createdAt_1');
    } catch (err) {
      if (err.code !== 26 && err.code !== 27) throw err;
    }
  },
  async down(db) {
    await db.collection('orders').createIndex(
      { status: 1, createdAt: 1 },
      { name: 'status_1_createdAt_1' }
    );
  }
};

Verify the Fix

Run the script twice in a row. No error on the second pass means the wrapper is doing its job.

Double-check collection and index state directly in mongosh:

// List all collections in the current database
db.getCollectionNames()

// Confirm the target is gone
db.getCollectionNames().includes('oldCollection') // โ†’ false

// Check remaining indexes on a collection
db.myCollection.getIndexes()

Prevention

  • Match on error code, not the message string. MongoDB has reworded the error text across versions; the numeric codes โ€” 26 for NamespaceNotFound, 27 for IndexNotFound โ€” haven't moved.
  • Make every migration idempotent from the start. Wrapping a drop costs two lines. Debugging a failed CI rerun costs a lot more.
  • Don't assume teardown ran cleanly in tests. Use beforeEach to set up fresh data rather than counting on a previous test's cleanup succeeding.
  • Spell-check collection names. user vs users produces the exact same error and is surprisingly easy to miss.

Related Error Notes