Skip to main content
This page aims to help current users of MongoDB Atlas Search make the transition to Meilisearch. For a high-level comparison of the two search engines, see Meilisearch vs MongoDB Atlas Search.

Overview

MongoDB Atlas Search is a full-text search engine built on Apache Lucene, integrated directly into MongoDB Atlas. It uses aggregation pipelines with the $search stage to query data. While this tight integration is convenient for MongoDB users, it also means your search is coupled to your database and constrained by the aggregation pipeline syntax. Meilisearch offers a simpler, faster alternative with typo tolerance, faceted search, and hybrid search out of the box — all through a straightforward REST API. This guide walks you through reading documents from a MongoDB collection and importing them into Meilisearch using a script in JavaScript, Python, or Ruby. You can also skip directly to the finished script. The migration process consists of four steps:
  1. Export your data from MongoDB
  2. Prepare your data for Meilisearch
  3. Import your data into Meilisearch
  4. Configure your Meilisearch index settings (optional)
To help with the transition, this guide also includes a comparison of settings and parameters, query types, and API methods. Before continuing, make sure you have Meilisearch installed and have access to a command-line terminal. If you’re unsure how to install Meilisearch, see our quick start.
This guide includes examples in JavaScript, Python, and Ruby. The packages used:

Export your MongoDB data

Initialize project

mkdir mongodb-meilisearch-migration
cd mongodb-meilisearch-migration
npm init -y
touch script.js

Install dependencies

npm install -s mongodb meilisearch

Create MongoDB client

You need your MongoDB connection string (URI). For Atlas, this is available in your cluster’s connection settings.
const { MongoClient } = require("mongodb");

const mongoClient = new MongoClient("MONGODB_URI");
Replace MONGODB_URI with your MongoDB connection string (for example, mongodb+srv://user:password@cluster.mongodb.net/myDatabase).

Fetch data from MongoDB

Use the find() method to retrieve all documents from a collection. For large collections, process documents in batches using a cursor.
const DB_NAME = "YOUR_DATABASE_NAME";
const COLLECTION_NAME = "YOUR_COLLECTION_NAME";
const BATCH_SIZE = 10000;

async function fetchAllDocuments() {
  await mongoClient.connect();
  const db = mongoClient.db(DB_NAME);
  const collection = db.collection(COLLECTION_NAME);

  const records = [];
  const cursor = collection.find({}).batchSize(BATCH_SIZE);

  for await (const doc of cursor) {
    records.push(doc);
  }

  return records;
}
Replace YOUR_DATABASE_NAME and YOUR_COLLECTION_NAME with your MongoDB database and collection names.

Prepare your data

MongoDB documents use _id as the primary key, which is typically an ObjectId. Meilisearch requires a string or integer primary key, so you need to convert _id to a string.
function prepareDocuments(docs) {
  return docs.map((doc) => {
    doc.id = doc._id.toString();
    delete doc._id;
    return doc;
  });
}
Meilisearch stores documents as flat JSON objects. If your MongoDB documents use deeply nested objects, only top-level fields can be used for filtering, sorting, and searching. You can keep nested objects for display purposes, but consider flattening fields you need to filter on. For example, { "author": { "name": "John" } } can stay as-is if you only display it, but you should add "author_name": "John" as a top-level field if you need to filter by author name.

Handle geo data

MongoDB uses GeoJSON for location data, typically stored as { type: "Point", coordinates: [longitude, latitude] }. Meilisearch uses a _geo object with lat and lng. Note that MongoDB stores coordinates in [longitude, latitude] order.
function convertGeoFields(doc, geoFieldName) {
  if (doc[geoFieldName] && doc[geoFieldName].type === "Point") {
    const coordinates = doc[geoFieldName].coordinates;
    doc._geo = {
      lat: coordinates[1], // GeoJSON: [lon, lat]
      lng: coordinates[0],
    };
    delete doc[geoFieldName];
  }
  return doc;
}

Import your data into Meilisearch

Create Meilisearch client

Create a Meilisearch client by passing the host URL and API key of your Meilisearch instance. The easiest option is to use the automatically generated admin API key.
const { MeiliSearch } = require("meilisearch");

const meiliClient = new MeiliSearch({
  host: "MEILI_HOST",
  apiKey: "MEILI_API_KEY",
});
const meiliIndex = meiliClient.index("MEILI_INDEX_NAME");
Replace MEILI_HOST, MEILI_API_KEY, and MEILI_INDEX_NAME with your Meilisearch host URL, API key, and target index name. Meilisearch will create the index if it doesn’t already exist.

Upload data to Meilisearch

Use the Meilisearch client method addDocumentsInBatches to upload all records in batches of 100,000.
const UPLOAD_BATCH_SIZE = 100000;
await meiliIndex.addDocumentsInBatches(documents, UPLOAD_BATCH_SIZE);
When you’re ready, run the script:
node script.js

Finished script

const { MongoClient } = require("mongodb");
const { MeiliSearch } = require("meilisearch");

const DB_NAME = "YOUR_DATABASE_NAME";
const COLLECTION_NAME = "YOUR_COLLECTION_NAME";
const FETCH_BATCH_SIZE = 10000;
const UPLOAD_BATCH_SIZE = 100000;

(async () => {
  // Connect to MongoDB
  const mongoClient = new MongoClient("MONGODB_URI");
  await mongoClient.connect();

  const db = mongoClient.db(DB_NAME);
  const collection = db.collection(COLLECTION_NAME);

  // Fetch all documents
  const records = [];
  const cursor = collection.find({}).batchSize(FETCH_BATCH_SIZE);

  for await (const doc of cursor) {
    records.push(doc);
  }

  await mongoClient.close();

  // Prepare documents for Meilisearch
  const documents = records.map((doc) => {
    doc.id = doc._id.toString();
    delete doc._id;
    return doc;
  });

  console.log(`Fetched ${documents.length} documents from MongoDB`);

  // Upload to Meilisearch
  const meiliClient = new MeiliSearch({
    host: "MEILI_HOST",
    apiKey: "MEILI_API_KEY",
  });
  const meiliIndex = meiliClient.index("MEILI_INDEX_NAME");

  await meiliIndex.addDocumentsInBatches(documents, UPLOAD_BATCH_SIZE);
  console.log("Migration complete");
})();

Configure your index settings

Meilisearch’s default settings deliver relevant, typo-tolerant search out of the box. Unlike MongoDB Atlas Search, which requires you to define search index mappings before you can search, Meilisearch indexes all fields automatically.

Key conceptual differences

MongoDB Atlas Search requires you to create search indexes with field mappings (dynamic or static) before you can run $search queries. Search behavior is configured through the aggregation pipeline using stages like $search, $searchMeta, and $vectorSearch. Each query requires constructing a pipeline with specific operators like text, compound, range, and near. Meilisearch takes a simpler approach: all fields are automatically indexed and searchable by default. You refine behavior through index settings (which affect all searches) and search parameters (which affect a single query). Features like typo tolerance, prefix search, and ranking work without any configuration. This means most Atlas Search index configurations have no direct equivalent in Meilisearch because the behavior is automatic. You don’t need to configure analyzers, define field mappings, or create search indexes before querying.

Configure embedders

If you used MongoDB Atlas Vector Search ($vectorSearch), you can replace it with Meilisearch’s built-in hybrid search. The key difference: with Atlas Vector Search, your application must compute vectors before indexing and searching. With Meilisearch, you configure an embedder once and Meilisearch handles all embedding automatically — both at indexing time and at search time. This means you can remove all embedding logic from your application code. Instead of calling an embedding API, computing vectors, and sending them alongside your aggregation pipeline, you simply send documents and text queries to Meilisearch. Configure an embedder source such as OpenAI, HuggingFace, or a custom REST endpoint:
curl -X PATCH 'MEILI_HOST/indexes/MEILI_INDEX_NAME/settings' \
  -H 'Authorization: Bearer MEILI_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "embedders": {
      "default": {
        "source": "openAi",
        "apiKey": "OPENAI_API_KEY",
        "model": "text-embedding-3-small",
        "documentTemplate": "A document titled {{doc.title}}: {{doc.description}}"
      }
    }
  }'
The documentTemplate controls what text is sent to the embedding model. Adjust it to match the fields in your documents. Meilisearch will automatically embed all existing documents and keep vectors up to date as you add, update, or delete documents. For more options including HuggingFace models, Ollama, and custom REST endpoints, see configuring embedders.
If you already have precomputed vectors stored alongside your MongoDB documents and want to keep them, you can include them in the _vectors field during migration and configure a userProvided embedder:
curl -X PATCH 'MEILI_HOST/indexes/MEILI_INDEX_NAME/settings' \
  -H 'Authorization: Bearer MEILI_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "embedders": {
      "default": {
        "source": "userProvided",
        "dimensions": 1536
      }
    }
  }'
Replace 1536 with the dimension of your vectors. With this approach, you remain responsible for computing and providing vectors when adding or updating documents. You also need to compute query vectors client-side when searching.To include vectors during migration, modify the prepareDocuments function to extract your vector field into _vectors:
// Inside prepareDocuments, if your MongoDB docs have a "embedding" field:
doc._vectors = { default: doc.embedding };
delete doc.embedding;

Configure filterable and sortable attributes

In MongoDB Atlas Search, you define which fields support faceting and filtering through your search index mappings. In Meilisearch, configure filterableAttributes and sortableAttributes:
curl -X PATCH 'MEILI_HOST/indexes/MEILI_INDEX_NAME/settings' \
  -H 'Authorization: Bearer MEILI_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "filterableAttributes": ["category", "price", "status", "_geo"],
    "sortableAttributes": ["price", "date", "_geo"]
  }'

What you gain

Migrating from MongoDB Atlas Search to Meilisearch gives you several advantages:
  • No search index definitions — Meilisearch indexes all fields automatically. No need to create or maintain search index mappings
  • Simpler query syntax — Replace complex aggregation pipelines with a flat JSON search request
  • Typo tolerance out of the box — no configuration required
  • Hybrid search combining keyword relevancy and semantic similarity in a single query, with automatic embedding
  • Faceted search with value distributions for building filter UIs
  • Highlighting of matching terms in results
  • Synonyms and stop words support
  • Decoupled search — Your search engine is independent of your database, so you can scale, tune, and deploy each separately

Settings and parameters comparison

The below tables compare MongoDB Atlas Search concepts with their Meilisearch equivalents.

Search index configuration

MongoDB Atlas SearchMeilisearchNotes
Dynamic field mappingsAutomaticMeilisearch indexes all fields by default
Static field mappingssearchableAttributes / filterableAttributesUse these to restrict or reorder searchable fields and enable filtering
Lucene analyzer (lucene.standard, etc.)AutomaticMeilisearch uses a built-in language-aware analyzer
Custom analyzersseparatorTokens / nonSeparatorTokensCustomize word boundary behavior
storedSourcedisplayedAttributesControl which fields appear in results
synonyms (in index definition)synonymsDefine equivalent terms
type: "string" mappingAutomaticField types are inferred
type: "number" mappingAutomaticField types are inferred
type: "geo" mapping_geo field with lat/lngAdd _geo to filterableAttributes and sortableAttributes

Search operators

MongoDB Atlas SearchMeilisearchNotes
$search with text operatorq search paramMeilisearch searches all searchableAttributes by default
$search with phrase operatorq search paramUse "quoted phrase" in the query string for phrase matching
$search with wildcard operatorAutomatic prefix searchMeilisearch applies prefix search on the last query word by default
$search with regex operatorNo direct equivalentUse filter for exact matching on specific field values
$search with compound (must/should/mustNot/filter)q + filterCombine search query with filter expressions using AND, OR, NOT
$search with range operatorfilter search paramUse operators like field > value or field value1 TO value2
$search with near (geo)_geoRadius(lat, lng, radius) or _geoBoundingBox([lat, lng], [lat, lng]) in filterRequires _geo in filterableAttributes
$vectorSearchhybrid + auto-embedderNo need to precompute vectors — Meilisearch embeds queries automatically
$searchMeta with facet collectorfacets search paramReturns value distributions for each facet
highlight option in $searchattributesToHighlight + highlightPreTag + highlightPostTagSearch params
$sort stage after $searchsort search paramRequires sortableAttributes
$skip / $limit stagesoffset / limit or page / hitsPerPageSearch params
scoreDetails: trueshowRankingScoreDetailsSearch param
count in $searchMetaAutomaticMeilisearch returns estimatedTotalHits (or totalHits with exhaustive pagination)

Index settings

MongoDB Atlas SearchMeilisearchNotes
Search index definitionAutomaticNo index definition needed before searching
Analyzer configurationAutomaticBuilt-in language-aware text processing
storedSourcedisplayedAttributesControl which fields appear in results
synonyms mappingsynonymsDefine equivalent terms
Index on specific fieldssearchableAttributesAll fields searchable by default; use this to restrict
Atlas cluster scalingAutomatic (Meilisearch Cloud)Meilisearch Cloud handles scaling

Query comparison

This section shows how common MongoDB Atlas Search aggregation pipelines translate to Meilisearch search requests. MongoDB Atlas Search:
db.collection.aggregate([
  {
    "$search": {
      "text": {
        "query": "search engine",
        "path": "title"
      }
    }
  }
])
Meilisearch:
{
  "q": "search engine"
}
Meilisearch searches all searchableAttributes by default. To restrict to a specific field, use the attributesToSearchOn search parameter. MongoDB Atlas Search:
db.collection.aggregate([
  {
    "$search": {
      "compound": {
        "must": [
          {
            "text": {
              "query": "laptop",
              "path": "title"
            }
          }
        ],
        "filter": [
          {
            "range": {
              "path": "price",
              "gte": 500,
              "lte": 2000
            }
          },
          {
            "text": {
              "query": "electronics",
              "path": "category"
            }
          }
        ]
      }
    }
  }
])
Meilisearch:
{
  "q": "laptop",
  "filter": "price >= 500 AND price <= 2000 AND category = electronics"
}
Attributes used in filter must first be added to filterableAttributes.

Sorting

MongoDB Atlas Search:
db.collection.aggregate([
  {
    "$search": {
      "text": {
        "query": "shoes",
        "path": "title"
      }
    }
  },
  { "$sort": { "price": 1, "date": -1 } }
])
Meilisearch:
{
  "q": "shoes",
  "sort": ["price:asc", "date:desc"]
}
Attributes used in sort must first be added to sortableAttributes.
MongoDB Atlas Search:
db.collection.aggregate([
  {
    "$searchMeta": {
      "facet": {
        "operator": {
          "text": {
            "query": "shoes",
            "path": "title"
          }
        },
        "facets": {
          "colorFacet": {
            "type": "string",
            "path": "color"
          },
          "priceFacet": {
            "type": "number",
            "path": "price",
            "boundaries": [0, 50, 100, 200]
          }
        }
      }
    }
  }
])
Meilisearch:
{
  "q": "shoes",
  "facets": ["color", "price"]
}
Meilisearch returns value distributions for each facet. Range aggregations with custom boundaries are not supported — use filter to narrow results by range. MongoDB Atlas Search:
db.collection.aggregate([
  {
    "$search": {
      "near": {
        "path": "location",
        "origin": {
          "type": "Point",
          "coordinates": [2.3522, 48.8566]
        },
        "pivot": 10000
      }
    }
  }
])
Meilisearch:
{
  "filter": "_geoRadius(48.8566, 2.3522, 10000)",
  "sort": ["_geoPoint(48.8566, 2.3522):asc"]
}
The _geo attribute must be added to both filterableAttributes and sortableAttributes. Note that MongoDB uses [longitude, latitude] order in GeoJSON while Meilisearch uses lat, lng order.
With Atlas Vector Search, you must compute the query vector yourself before searching. With Meilisearch, you configure an auto-embedder once and just send natural language queries: MongoDB Atlas Vector Search:
db.collection.aggregate([
  {
    "$vectorSearch": {
      "index": "vector_index",
      "path": "embedding",
      "queryVector": [0.1, 0.2, 0.3, "..."],
      "numCandidates": 100,
      "limit": 10
    }
  }
])
Meilisearch:
{
  "q": "comfortable running shoes",
  "hybrid": {
    "semanticRatio": 1.0,
    "embedder": "default"
  },
  "limit": 10
}
With an auto-embedder configured, Meilisearch embeds the q text for you. Setting semanticRatio to 1.0 performs pure semantic search — but without managing vectors in your application code. Set semanticRatio to 0.5 to combine keyword and semantic results in a single hybrid query, something that would require running both $search and $vectorSearch pipelines separately with Atlas.

API methods

This section compares MongoDB Atlas Search operations with Meilisearch API endpoints.
OperationMongoDB Atlas SearchMeilisearch
Create search indexdb.collection.createSearchIndex()POST /indexes (automatic)
Delete search indexdb.collection.dropSearchIndex()DELETE /indexes/{index_uid}
List search indexesdb.collection.getSearchIndexes()GET /indexes
Full-text searchdb.collection.aggregate([{ $search: ... }])POST /indexes/{index_uid}/search
Vector searchdb.collection.aggregate([{ $vectorSearch: ... }])POST /indexes/{index_uid}/search (with hybrid)
Facet searchdb.collection.aggregate([{ $searchMeta: ... }])POST /indexes/{index_uid}/search (with facets)
Multi-searchMultiple aggregation pipelinesPOST /multi-search
Add documentsdb.collection.insertMany()POST /indexes/{index_uid}/documents
Get documentdb.collection.findOne()GET /indexes/{index_uid}/documents/{id}
Delete documentdb.collection.deleteOne()DELETE /indexes/{index_uid}/documents/{id}
Delete by filterdb.collection.deleteMany()POST /indexes/{index_uid}/documents/delete
Update settingsUpdate search index definitionPATCH /indexes/{index_uid}/settings
Get settingsdb.collection.getSearchIndexes()GET /indexes/{index_uid}/settings
API keysAtlas access managementPOST /keys
Health checkAtlas monitoringGET /health
Task statusAtlas index build statusGET /tasks/{task_uid}

Front-end components

MongoDB Atlas Search does not include dedicated front-end search components. Meilisearch is compatible with Algolia’s InstantSearch libraries through Instant Meilisearch, giving you pre-built widgets for search boxes, hit displays, facet filters, pagination, and more. You can find an up-to-date list of the components supported by Instant Meilisearch in the GitHub project’s README.