Meilisearch → Elastic Cloud
Move a Meilisearch index to Elastic Cloud when you need mappings, bulk updates, or aggregations Meilisearch is not covering. Define the mapping first. A 2023 report of indexing lag is a workload to retest, not a current benchmark.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
Meilisearch
- Open-source typo-tolerant search with a small API and an optional managed cloud
- Managed indexing, relevance, filtering, and globally available query APIs reduce search operations
- Primary keys, ranking rules, and heavy update workloads need explicit setup that a hosted index used to hide
- Ranking behavior, analyzers, schemas, and learned signals require careful revalidation after migration
Elastic Cloud
- Managed Elasticsearch delivers powerful full-text, vector, analytics, and observability capabilities at scale
- Managed indexing, relevance, filtering, and globally available query APIs reduce search operations
- Mappings, analyzers, cluster sizing, upgrades, and query complexity require search expertise
- Ranking behavior, analyzers, schemas, and learned signals require careful revalidation after migration
Elastic Cloud: Managed Elasticsearch delivers powerful full-text, vector, analytics, and observability capabilities at scale. This removes a major source-side concern: Primary keys, ranking rules, and heavy update workloads need explicit setup that a hosted index used to hide.
What you lose: Open-source typo-tolerant search with a small API and an optional managed cloud. What you inherit: Mappings, analyzers, cluster sizing, upgrades, and query complexity require search expertise.
Jump to a section
Know the shape of the move.
This timeline assumes
- Elastic Cloud is the managed Elasticsearch cluster. schappim's 2023 note says Elastic, not a named Cloud region.
- The site already documents Algolia → Elastic Cloud and Meilisearch → Typesense Cloud.
- You can create an index with an explicit mapping before the first bulk load.
- The database or the Meilisearch index is identified as the source of truth before you copy anything.
- Meilisearch stays up until Elastic Cloud has served a representative update day.
What survives the move.
“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.
| Item | Outcome | Impact | What happens | Mitigation |
|---|---|---|---|---|
| Documents | partial | critical | Elasticsearch's bulk API indexes batches of action-and-document lines. Meilisearch documents become _source documents. Nothing in a settings export creates the Elasticsearch index for you. | Bulk load into an empty index and compare counts after the cluster reports the bulk as done. |
| Ids | partial | critical | Meilisearch ids are integers or strings limited to letters, digits, hyphens, and underscores. Use that value as Elasticsearch _id so a rerun overwrites instead of duplicating. | Set _id from the primary key on every bulk line. |
| Mappings | manual | critical | Meilisearch will index a new field it has not seen before. Elasticsearch dynamic mapping can guess a type you did not want, and a later conflict rejects the document. | Put the mapping in the index template before the first document. Turn dynamic mapping off if you want rejects instead of guesses. |
| Filters | partial | high | Meilisearch filter expressions become Elasticsearch filters in the query DSL. The syntax is not a rename. | Rewrite each production filter and run it against a known document. |
| Ranking | manual | critical | Meilisearch rankingRules do not become a BM25 query. Meilisearch's own comparison says Elasticsearch relevancy takes analyzers, mappings, and fuzziness, and their product is aimed at less of that setup. | Build the query you mean. Compare it with Meilisearch on a saved list before cutover. |
| Typo tolerance | manual | high | Meilisearch applies typo tolerance unless you disable it. Elasticsearch needs a fuzzy query or an equivalent. The default match query will not behave the same. | Decide which fields are fuzzy. Test a one-character typo on each. |
| Aggregations | partial | high | This is a common reason to leave. On 11 October 2025, dewey wrote on Hacker News that a Rails app moved from Meilisearch to Elastic for more complex aggregation queries. Facets you had in Meilisearch still need an aggregation that matches the mapping. | List the facet queries. Write the aggregation and compare bucket counts. |
| Update throughput | manual | high | On 8 February 2023, schappim wrote that about 50,000 constantly changing ecommerce records fell hours behind in Meilisearch, so that part of the app moved to Elastic. Kerollmops replied that indexing had improved and asked which version they ran. Measure your version. Do not treat 2023 lag as today's number. | Replay a day of updates against the Meilisearch version you run, and against Elastic Cloud, before you commit. |
| Stop words and synonyms | manual | medium | Meilisearch stop words and synonyms are index settings. Elasticsearch uses analyzers and synonym token filters, which belong in the mapping. | Translate them into the analyzer. Reindex if you change that analyzer later. |
| Client libraries | manual | medium | schappim also wrote that Meilisearch Rails gems lagged the server. An Elastic client is a separate dependency with its own version pin. | Pin the Elasticsearch client to the Cloud version you deploy. |
| Geo | partial | medium | A Meilisearch _geo field becomes a geo_point mapping. Queries change from _geoRadius filters to geo queries. | Map the field as geo_point before load. A wrong first mapping means a reindex. |
| Operations | manual | high | Meilisearch's comparison says Elasticsearch clusters involve shards, replicas, and heap sizing, and that this is a reason some teams leave. Elastic Cloud manages the machines and still leaves index design to you. | Set shard and replica counts with the data size you measured, then load. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| Index | Elasticsearch index or alias | manual | Create the mapping first. |
| Primary key | _id | transform | So a rerun overwrites. |
| Document field | Mapped property | transform | Type it. Do not trust dynamic mapping for production. |
| Filter expression | Query DSL filter | manual | Rewrite. Do not string-replace. |
| Ranking rule | Query and field boosts | manual | BM25 is not rankingRules. |
| Typo tolerance | Fuzzy query | manual | Only on the fields you choose. |
| Facet | Aggregation | manual | Bucket counts need a check. |
| Synonym | Synonym token filter | manual | Lives in the analyzer. Reindex to apply. |
| Stop word | Stop filter | manual | Same analyzer constraint. |
| _geo | geo_point | transform | Map it before the first bulk. |
Make the move recoverable.
Create the source-of-truth backup
Keep Meilisearch and a mapping file that can recreate the Elastic Cloud index.
- Record document counts and the primary key.
- Export Meilisearch settings, stop words, and synonyms.
- Commit the Elasticsearch mapping and index template.
- Snapshot the source database if that is the system of record.
- Leave Meilisearch serving until the observation window ends.
Proof to capture: A count, a mapping file, and a Meilisearch host you can still query.
Mapping template
Stop dynamic mapping from guessing types.
- Declare types for every field you filter or aggregate.
- Set dynamic to strict if unknown fields should fail.
- Create the index before the bulk load.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Bulk load
Copy documents with stable ids.
- Write action lines with _id set to the Meilisearch primary key.
- Send batches the cluster can accept.
- Read the bulk response. A 200 can still contain item errors.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
The things most likely to hurt.
These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.
Dynamic mapping guesses a type
The first documents teach Elasticsearch a type, and later documents conflict.
- Consequence
- Those documents are rejected and the index looks complete.
- Mitigation
- Use an explicit mapping and read every bulk item error.
Stop if: The bulk response contains mapping errors.
The 2023 lag story is treated as current
A team chooses Elastic Cloud only because of schappim's February 2023 comment.
- Consequence
- You take on cluster operations you may not need on today's Meilisearch.
- Mitigation
- Replay your updates on the version you run.
Stop if: Nobody has measured the current task queue.
Queries move with the client unchanged
The app still sends Meilisearch filter syntax.
- Consequence
- Searches fail or return the wrong set.
- Mitigation
- Rewrite filters and fuzzy behavior in the same deploy as the client.
Stop if: A production filter has no Elasticsearch equivalent in the branch.
Do the work in this order.
- Days 1–4 · inventory
Queries and types
8–14 hours active2–4 days elapsedSearch review waiting- List fields, filters, fuzzy fields, and aggregations.
- Decide whether the database or Meilisearch is the source of truth.
Depends on: Meilisearch admin and an Elastic Cloud deployment
Stop / go checkpointWrite the mapping?
Go when: Every production field has a type.
Stop when: An aggregation has no source field.
- Days 4–7 · backup
Counts and settings
3–6 hours active1–3 days elapsedSnapshot waiting- Record counts.
- Export synonyms and stop words.
Depends on: Field list
Stop / go checkpointBulk load?
Go when: The mapping file is committed.
Stop when: The mapping is still being edited on the cluster by hand.
- Days 7–16 · pilot
Load and compare
16–40 hours active6–10 days elapsedQuery review waiting- Bulk load into the empty index.
- Rewrite a sample of filters, fuzzy queries, and aggregations.
Depends on: Mapping
Stop / go checkpointShadow?
Go when: Counts match and item errors are zero.
Stop when: A mapping conflict appears.
- Days 16–22 · cutover
Switch writes and reads
6–14 hours active2–4 days elapsedDeploy waiting- Point updates at Elastic Cloud.
- Switch reads after the alias is caught up.
Depends on: Clean pilot
Stop / go checkpointStop Meilisearch writes?
Go when: Reads and writes use Elastic Cloud.
Stop when: A client still sends Meilisearch syntax.
- Days 20–28 · observe
Update day
6–12 hours active4–8 days elapsedA busy update day waiting- Watch bulk rejections and search errors.
- Keep Meilisearch queryable.
Depends on: Cutover
Stop / go checkpointClose rollback?
Go when: Updates and the signed queries hold.
Stop when: Item errors or empty aggregations return.
Cut over with a way back.
Cutover
Read and write search on Elastic Cloud with stable _ids and a reviewed query set.
- Bulk load the delta with the same _ids.
- Point the update job at the cluster.
- Switch the application query path.
- Run the signed filters and one typo query.
- Leave Meilisearch in place.
Proof to capture: The app queries Elastic Cloud. Bulk item errors on the delta are zero. Meilisearch is not receiving new writes.
Rollback
Point reads and writes back at Meilisearch.
- Deploy the Meilisearch client.
- Point the update job back.
- Confirm a search and an update.
- Keep the Elastic Cloud index for the retry.
- Save the bulk error file if one caused the rollback.
Proof to capture: Meilisearch serves production search again.
- Mapping conflicts in the bulk response
- Counts do not match
- A production filter has no DSL equivalent
- Aggregation buckets disagree
- Updates reject after cutover
Prove the migration worked.
Every blocking check must pass. Capture the evidence before cleanup begins.
| Pass | ID | Check | Method | Expected result | Evidence |
|---|---|---|---|---|---|
V-01Blocking | Explicit types | Read the index mapping and compare it with the template. | Production fields match the template. Dynamic surprises are absent. | Mapping JSON. | |
V-02Blocking | Item errors | Scan the bulk response for errors. | Zero item errors. | Bulk log. | |
V-03Blocking | Document count | Compare _count with Meilisearch after refresh. | Counts match. | Count sheet. | |
V-04Blocking | _id stability | Reindex one known document and GET it by the Meilisearch id. | One document, updated in place. | GET response. | |
V-05Blocking | Rewritten filters | Run each signed filter. | The same documents come back, or the difference is signed. | Filter diff. | |
V-06Blocking | Fuzzy fields | Search a one-character typo on each fuzzy field. | The intended document is returned where you enabled fuzziness. | Typo sample. | |
V-07Blocking | Bucket counts | Compare facet buckets with Meilisearch. | Buckets match or the gap is explained. | Aggregation diff. | |
V-08Blocking | A busy batch | Replay a production-sized update batch. | Bulk succeeds and search reflects the new documents. | Update log. |
Remove the scaffolding safely.
Safe after: One busy update day on Elastic Cloud, with Meilisearch still queryable.
- Commit the index template.
- Archive bulk logs and the query diff.
- Rotate the Meilisearch key used for the export.
- Keep Meilisearch until rollback closes.
- Remove the old client dependency in a later deploy.
- Review shard sizing after real volume.
When the plan met reality.
First-hand accounts are preferred. Vendor case studies are labeled, and every note below is an editorial paraphrase—follow the link for the full context.
On 8 February 2023, schappim wrote that Meilisearch worked for records that rarely change, and fell hours behind on a fast ecommerce catalog of about 50,000 constantly changing records. That part of the app moved to Elastic. He also hit a stretch where the Rails gem did not match the server. Kerollmops from Meilisearch replied the same day that indexing had improved with auto-batching, and asked which version was in use. The comment says Elastic, not Elastic Cloud, and it does not name a cluster size.
- Replay a production-sized update batch and measure lag before you trust a quiet docs index as your model.
- Treat the hours of lag as that 2023 install. Kerollmops said indexing had changed and asked for the version.
- The split he describes is Meilisearch for quieter data and Elastic for the catalog that changes all day.
- A gem and server mismatch was a second problem, separate from raw indexing speed.
On 11 October 2025, dewey commented on a Typesense thread that a Rails app had gone from Meilisearch, which he calls great, to Elastic for more complex aggregation queries. He was about to try Typesense for instant-search behavior. The comment is one sentence. It does not include an index size, a mapping, or a statement that the cluster was Elastic Cloud.
- If aggregations are the reason you are leaving, test those queries on Elastic before you shut Meilisearch off.
- Keep this comment in its lane. It is a reason, not a migration write-up with counts or a cutover.
- Aggregations are the gap he names between Meilisearch and Elastic.
- He was already looking at Typesense next, so Elastic is not presented as a final home.
Verify against the primary material.
Platform behavior changes. Check these sources and the review dates above before executing a production migration.
- Elasticsearch: bulk APIAccessed 2026-11-01
- Elasticsearch: mappingAccessed 2026-11-01
- Meilisearch: primary keyAccessed 2026-11-01
- Meilisearch: comparison with ElasticsearchAccessed 2026-11-01
- Hacker News: Meilisearch 1.0 indexing threadAccessed 2026-11-01
- Hacker News: Meilisearch to Elastic for aggregationsAccessed 2026-11-01