Fix: MongoDB '$search is only allowed as the first stage' Error

intermediate🍃 MongoDB2026-07-23| MongoDB Atlas (Version 4.2+), Node.js, Mongoose, MongoDB Compass

Error Message

PlanExecutor error during aggregation :: caused by :: $search is only allowed as the first stage in the pipeline
#mongodb#atlas-search#aggregation-pipeline#database-optimization

Why Your Aggregation Pipeline Is Failing

You’ll hit this roadblock if you try to use the $search operator anywhere except the very start of your aggregation array. Unlike standard operators, Atlas Search requires the driver to hand off the query to a specialized engine immediately.

PlanExecutor error during aggregation :: caused by :: $search is only allowed as the first stage in the pipeline

The Technical Reason: mongot vs. mongod

Under the hood, MongoDB Atlas runs two separate processes. The standard mongod engine handles regular queries, while a process called mongot manages Lucene-based Atlas Search.

When you trigger an aggregation, MongoDB needs to know right away if it should involve the mongot process. If you place a $match or $project stage first, the mongod engine starts processing the data itself. By the time it hits $search, it's too late to hand the operation over to the search engine, causing the pipeline to crash.

The Common Mistake

Developers often try to narrow down data using a $match stage before performing a search. While this feels intuitive for performance, it’s the primary cause of this error. Take a look at this broken example:

db.products.aggregate([
  { $match: { status: "active" } }, // ❌ Error! This cannot come before $search
  {
    $search: {
      index: "default",
      text: {
        query: "mechanical keyboard",
        path: "name"
      }
    }
  }
]);

Solution 1: Reorder Your Stages

Your quickest path to a fix is moving $search to the top of the array. Any filtering, limiting, or sorting must happen afterward.

db.products.aggregate([
  {
    $search: {
      index: "default",
      text: {
        query: "mechanical keyboard",
        path: "name"
      }
    }
  },
  { $match: { status: "active" } } // ✅ This works
]);

Note on Performance: While this stops the error, it isn't always efficient. If you have 10 million documents but only 1,000 are "active," MongoDB still has to search the entire index before the $match stage can discard the inactive ones.

Solution 2: Use the Compound Operator (Best Practice)

To keep your queries fast, use the compound operator. This allows you to combine your search terms and your filters into a single operation that stays inside the mongot engine. In a collection of 5 million documents, this method can reduce execution time from several seconds to under 100ms.

db.products.aggregate([
  {
    $search: {
      index: "default",
      compound: {
        must: [{
          text: {
            query: "mechanical keyboard",
            path: "name"
          }
        }],
        filter: [{
          text: {
            query: "active",
            path: "status"
          }
        }]
      }
    }
  }
]);

Here is the breakdown of why this works better:

  • must: This is your search criteria. It contributes to the relevance score.
  • filter: This works exactly like a $match. It includes or excludes documents without messing with your search rankings.

Solution 3: Watch Out for Mongoose Middleware

If your code looks correct but you still see the error, check your Mongoose plugins. Global plugins for "soft deletes" often inject a { deleted: false } match stage at the start of every query automatically. This hidden stage will break your Atlas Search.

To bypass this, you must construct your pipeline manually and ensure $search is at index 0:

const pipeline = [
  { $search: { /* your config */ } },
  { $limit: 10 }
];

// Use the base model to avoid middleware interference if necessary
await Product.aggregate(pipeline);

How to Verify the Fix

Run your query in the MongoDB Compass Aggregation Pipeline Builder. Compass provides a real-time preview of each stage. If the first stage isn't $search, the preview will immediately show the PlanExecutor error. Additionally, try adding .explain("executionStats") to your query. You should see a SEARCH stage at the top of the execution tree rather than a COLLSCAN or IXSCAN.

Key Takeaways

  • Positioning: $search is a diva; it must always come first.
  • Filtering: Use compound and filter inside the search stage for maximum speed.
  • Hosting: Remember that $search only works on MongoDB Atlas. If you are on a local community edition, you must use $text (which also has specific placement rules).

Related Error Notes