` contains a value that is not in the filterable attributes list.
## `invalid_multi_search_sort_facet_values_by`
`federation.mergeFacets.sortFacetValuesBy` is not a string or doesn't have one of the allowed values.
## `invalid_multi_search_query_facets`
A query in the queries array contains `facets` when federation is present and non-`null`.
## `invalid_multi_search_merge_facets`
`federation.mergeFacets` is not an object or contains unexpected fields.
## `invalid_multi_search_max_values_per_facet`
`federation.mergeFacets.maxValuesPerFacet` is not a positive integer.
## `invalid_multi_search_facet_order`
Two or more indexes have a different `faceting.sortFacetValuesBy` for the same requested facet.
## `invalid_multi_search_facets_by_index`
`facetsByIndex` is not an object or contains unknown fields.
## `invalid_multi_search_remote`
`federationOptions.remote` is not `network.self` and is not a key in `network.remotes`.
## `invalid_network_self`
The [network object](/docs/reference/api/network/get-network) contains a `self` that is not a string or `null`.
## `invalid_network_remotes`
The [network object](/docs/reference/api/network/get-network) contains a `remotes` that is not an object or `null`.
## `invalid_network_url`
One of the remotes in the [network object](/docs/reference/api/network/get-network) contains a `url` that is not a string.
## `invalid_network_search_api_key`
One of the remotes in the [network object](/docs/reference/api/network/get-network) contains a `searchApiKey` that is not a string or `null`.
## `invalid_search_attributes_to_crop`
The [`attributesToCrop`](/docs/reference/api/search/search-with-post#body-attributes-to-crop) parameter is invalid. It should be an array of strings, a string, or set to `null`.
## `invalid_search_attributes_to_highlight`
The [`attributesToHighlight`](/docs/reference/api/search/search-with-post#body-attributes-to-highlight) parameter is invalid. It should be an array of strings, a string, or set to `null`.
## `invalid_search_attributes_to_retrieve`
The [`attributesToRetrieve`](/docs/reference/api/search/search-with-post#body-attributes-to-retrieve) parameter is invalid. It should be an array of strings, a string, or set to `null`.
## `invalid_search_crop_length`
The [`cropLength`](/docs/reference/api/search/search-with-post#body-crop-length) parameter is invalid. It should be an integer.
## `invalid_search_crop_marker`
The [`cropMarker`](/docs/reference/api/search/search-with-post#body-crop-marker) parameter is invalid. It should be a string or set to `null`.
## `invalid_search_embedder`
[`embedder`](/docs/reference/api/search/search-with-post#body-hybrid) is invalid. It should be a string corresponding to the name of a configured embedder.
## `invalid_search_facets`
This error occurs if:
* The [`facets`](/docs/reference/api/search/search-with-post#body-facets) parameter is invalid. It should be an array of strings, a string, or set to `null`
* The attribute used for faceting is not defined in the [`filterableAttributes` list](/docs/reference/api/settings/get-filterableattributes)
## `invalid_search_filter`
This error occurs if:
* The syntax for the [`filter`](/docs/reference/api/search/search-with-post#body-filter) parameter is invalid
* The attribute used for filtering is not defined in the [`filterableAttributes` list](/docs/reference/api/settings/get-filterableattributes)
* A reserved keyword like `_geo`, `_geoDistance`, or `_geoPoint` is used as a filter
## `invalid_search_highlight_post_tag`
The [`highlightPostTag`](/docs/reference/api/search/search-with-post#body-highlight-pre-tag) parameter is invalid. It should be a string.
## `invalid_search_highlight_pre_tag`
The [`highlightPreTag`](/docs/reference/api/search/search-with-post#body-highlight-pre-tag) parameter is invalid. It should be a string.
## `invalid_search_hits_per_page`
The [`hitsPerPage`](/docs/reference/api/search/search-with-post#body-hits-per-page) parameter is invalid. It should be an integer.
## `invalid_search_hybrid_query`
The [`hybrid`](/docs/reference/api/search/search-with-post#body-hybrid) parameter is neither `null` nor an object, or it is an object with unknown keys.
## `invalid_search_limit`
The [`limit`](/docs/reference/api/search/search-with-post#body-limit) parameter is invalid. It should be an integer.
## `invalid_search_locales`
The [`locales`](/docs/reference/api/search/search-with-post#body-locales) parameter is invalid.
## `invalid_settings_embedder`
The [`embedders`](/docs/reference/api/settings/get-embedders) index setting value is invalid.
## `invalid_settings_facet_search`
The [`facetSearch`](/docs/reference/api/settings/get-facetsearch) index setting value is invalid.
## `invalid_settings_localized_attributes`
The [`localizedAttributes`](/docs/reference/api/settings/get-localizedattributes) index setting value is invalid.
## `invalid_search_matching_strategy`
The [`matchingStrategy`](/docs/reference/api/search/search-with-post#body-matching-strategy) parameter is invalid. It should either be set to `last` or `all`.
## `invalid_search_offset`
The [`offset`](/docs/reference/api/search/search-with-post#body-offset) parameter is invalid. It should be an integer.
## `invalid_settings_prefix_search`
The [`prefixSearch`](/docs/reference/api/settings/get-prefixsearch) index setting value is invalid.
## `invalid_search_page`
The [`page`](/docs/reference/api/search/search-with-post#body-page) parameter is invalid. It should be an integer.
## `invalid_search_q`
The [`q`](/docs/reference/api/search/search-with-post#body-q) parameter is invalid. It should be a string or set to `null`
## `invalid_search_ranking_score_threshold`
The [`rankingScoreThreshold`](/docs/reference/api/search/search-with-post#body-show-ranking-score-threshold) in a search or multi-search request is not a number between `0.0` and `1.0`.
## `invalid_search_show_matches_position`
The [`showMatchesPosition`](/docs/reference/api/search/search-with-post#body-show-matches-position) parameter is invalid. It should either be a boolean or set to `null`.
## `invalid_search_sort`
This error occurs if:
* The syntax for the [`sort`](/docs/reference/api/search/search-with-post#body-sort) parameter is invalid
* The attribute used for sorting is not defined in the [`sortableAttributes`](/docs/reference/api/settings/get-sortableattributes) list or the `sort` ranking rule is missing from the settings
* A reserved keyword like `_geo`, `_geoDistance`, `_geoRadius`, or `_geoBoundingBox` is used as a filter
## `invalid_settings_displayed_attributes`
The value of [displayed attributes](/docs/capabilities/full_text_search/how_to/configure_displayed_attributes#displayed-fields) is invalid. It should be an empty array, an array of strings, or set to `null`.
## `invalid_settings_distinct_attribute`
The value of [distinct attributes](/docs/capabilities/full_text_search/how_to/configure_distinct_attribute) is invalid. It should be a string or set to `null`.
## `invalid_settings_faceting_sort_facet_values_by`
The value provided for the [`sortFacetValuesBy`](/docs/reference/api/settings/get-faceting) object is incorrect. The accepted values are `alpha` or `count`.
## `invalid_settings_faceting_max_values_per_facet`
The value for the [`maxValuesPerFacet`](/docs/reference/api/settings/get-faceting) field is invalid. It should either be an integer or set to `null`.
## `invalid_settings_filterable_attributes`
The value of [filterable attributes](/docs/reference/api/settings/get-filterableattributes) is invalid. It should be an empty array, an array of strings, or set to `null`.
## `invalid_settings_pagination`
The value for the [`maxTotalHits`](/docs/reference/api/settings/update-pagination) field is invalid. It should either be an integer or set to `null`.
## `invalid_settings_ranking_rules`
This error occurs if:
* The [settings payload](/docs/reference/api/settings/update-all-settings) has an invalid format
* A non-existent ranking rule is specified
* A custom ranking rule is malformed
* A reserved keyword like `_geo`, `_geoDistance`, `_geoRadius`, `_geoBoundingBox`, or `_geoPoint` is used as a custom ranking rule
## `invalid_settings_searchable_attributes`
The value of [searchable attributes](/docs/reference/api/settings/get-searchableattributes) is invalid. It should be an empty array, an array of strings or set to `null`.
## `invalid_settings_search_cutoff_ms`
The specified value for [`searchCutoffMs`](/docs/reference/api/settings/update-searchcutoffms) is invalid. It should be an integer indicating the cutoff in milliseconds.
## `invalid_settings_sortable_attributes`
The value of [sortable attributes](/docs/reference/api/settings/get-sortableattributes) is invalid. It should be an empty array, an array of strings or set to `null`.
## `invalid_settings_stop_words`
The value of [stop words](/docs/reference/api/settings/get-stopwords) is invalid. It should be an empty array, an array of strings or set to `null`.
## `invalid_settings_synonyms`
The value of the [synonyms](/docs/reference/api/settings/get-synonyms) is invalid. It should either be an object or set to `null`.
## `invalid_settings_typo_tolerance`
This error occurs if:
* The [`enabled`](/docs/reference/api/settings/get-typotolerance) field is invalid. It should either be a boolean or set to `null`
* The [`disableOnAttributes`](/docs/reference/api/settings/get-typotolerance) field is invalid. It should either be an array of strings or set to `null`
* The [`disableOnWords`](/docs/reference/api/settings/get-typotolerance) field is invalid. It should either be an array of strings or set to `null`
* The [`minWordSizeForTypos`](/docs/reference/api/settings/get-typotolerance) field is invalid. It should either be an integer or set to `null`
* The value of either [`oneTypo`](/docs/reference/api/settings/get-typotolerance) or [`twoTypos`](/docs/reference/api/settings/get-typotolerance) is invalid. It should either be an integer or set to `null`
## `invalid_similar_id`
The provided target document identifier is invalid. A document identifier can be of type integer or string, only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and underscores (\_).
## `not_found_similar_id`
Meilisearch could not find the target document. Make sure your target document identifier corresponds to a document in your index.
## `invalid_similar_attributes_to_retrieve`
[`attributesToRetrieve`](/docs/reference/api/search/search-with-post#body-attributes-to-retrieve) is invalid. It should be an array of strings, a string, or set to null.
## `invalid_similar_embedder`
[`embedder`](/docs/reference/api/similar-documents/get-similar-documents-with-post) is invalid. It should be a string corresponding to the name of a configured embedder.
## `invalid_similar_filter`
[`filter`](/docs/reference/api/search/search-with-post#body-filter) is invalid or contains a filter expression with a missing or invalid operator. Filter expressions must be a string, array of strings, or array of array of strings for the POST endpoint. It must be a string for the GET endpoint.
Meilisearch also throws this error if the attribute used for filtering is not defined in the `filterableAttributes` list.
## `invalid_similar_limit`
[`limit`](/docs/reference/api/search/search-with-post#body-limit) is invalid. It should be an integer.
## `invalid_similar_offset`
[`offset`](/docs/reference/api/search/search-with-post#body-offset) is invalid. It should be an integer.
## `invalid_similar_show_ranking_score`
[`ranking_score`](/docs/reference/api/search/search-with-post#body-show-ranking-score) is invalid. It should be a boolean.
## `invalid_similar_show_ranking_score_details`
[`ranking_score_details`](/docs/reference/api/search/search-with-post#body-show-ranking-score-details) is invalid. It should be a boolean.
## `invalid_similar_ranking_score_threshold`
The [`rankingScoreThreshold`](/docs/reference/api/search/search-with-post#body-show-ranking-score-threshold) in a similar documents request is not a number between `0.0` and `1.0`.
## `invalid_state`
The database is in an invalid state. Deleting the database and re-indexing should solve the problem.
## `invalid_store_file`
The `data.ms` folder is in an invalid state. Your `b` file is corrupted or the `data.ms` folder has been replaced by a file.
## `invalid_swap_duplicate_index_found`
The indexes used in the [`indexes`](/docs/reference/api/indexes/swap-indexes) array for a [swap index](/docs/reference/api/indexes/swap-indexes) request have been declared multiple times. You must declare each index only once.
## `invalid_swap_indexes`
This error happens if:
* The payload doesn't contain exactly two index [`uids`](/docs/reference/api/indexes/swap-indexes) for a swap operation
* The payload contains an invalid index name in the [`indexes`](/docs/reference/api/indexes/swap-indexes) array
## `invalid_task_after_enqueued_at`
The [`afterEnqueuedAt`](/docs/reference/api/tasks/list-tasks) query parameter is invalid.
## `invalid_task_after_finished_at`
The [`afterFinishedAt`](/docs/reference/api/tasks/list-tasks) query parameter is invalid.
## `invalid_task_after_started_at`
The [`afterStartedAt`](/docs/reference/api/tasks/list-tasks) query parameter is invalid.
## `invalid_task_before_enqueued_at`
The [`beforeEnqueuedAt`](/docs/reference/api/tasks/list-tasks) query parameter is invalid.
## `invalid_task_before_finished_at`
The [`beforeFinishedAt`](/docs/reference/api/tasks/list-tasks) query parameter is invalid.
## `invalid_task_before_started_at`
The [`beforeStartedAt`](/docs/reference/api/tasks/list-tasks) query parameter is invalid.
## `invalid_task_canceled_by`
The [`canceledBy`](/docs/reference/api/tasks/list-tasks) query parameter is invalid. It should be an integer. Multiple `uid`s should be separated by commas (`,`).
## `invalid_task_index_uids`
The [`indexUids`](/docs/reference/api/tasks/list-tasks) query parameter contains an invalid index uid.
## `invalid_task_limit`
The [`limit`](/docs/reference/api/tasks/list-tasks) parameter is invalid. It must be an integer.
## `invalid_task_statuses`
The requested task status is invalid. Please use one of the [possible values](/docs/reference/api/tasks/get-task).
## `invalid_task_types`
The requested task type is invalid. Please use one of the [possible values](/docs/reference/api/tasks/get-task).
## `invalid_task_uids`
The [`uids`](/docs/reference/api/tasks/list-tasks) query parameter is invalid.
## `invalid_webhooks`
The create webhook request did not contain a valid JSON payload. Meilisearch also returns this error when you try to create more than 20 webhooks.
## `invalid_webhook_url`
The provided webhook URL isn’t a valid JSON string, is `null`, is missing, or its value cannot be parsed as a valid URL.
## `invalid_webhook_headers`
The provided webhook `headers` field is not a JSON object or not a valid HTTP header. Meilisearch also returns this error if you set more than 200 header fields for a single webhook.
## `invalid_webhook_uuid`
The provided webhook `uuid` is not a valid uuid v4 value.
## `io_error`
This error generally occurs when the host system has no space left on the device or when the database doesn't have read or write access.
## `index_primary_key_no_candidate_found`
[Primary key inference](/docs/resources/internals/primary_key#meilisearch-guesses-your-primary-key) failed as the received documents do not contain any fields ending with `id`. [Manually designate the primary key](/docs/resources/internals/primary_key#setting-the-primary-key), or add some field ending with `id` to your documents.
## `malformed_payload`
The [Content-Type header](/docs/reference/api/headers) does not match the request body payload format or the format is invalid.
## `missing_api_key_actions`
The [`actions`](/docs/reference/api/keys/list-api-keys) field is missing from payload.
## `missing_api_key_expires_at`
The [`expiresAt`](/docs/reference/api/keys/get-api-key#response-expiresat) field is missing from payload.
## `missing_api_key_indexes`
The [`indexes`](/docs/reference/api/keys/get-api-key#response-indexes) field is missing from payload.
## `missing_authorization_header`
This error happens if:
* The requested resources are protected with an API key that was not provided in the request header. Check our [security tutorial](/docs/resources/self_hosting/security/basic_security) for more information
## `missing_content_type`
The payload does not contain a [Content-Type header](/docs/reference/api/headers). Currently, Meilisearch only supports JSON, CSV, and NDJSON.
## `missing_document_filter`
This payload is missing the [`filter`](/docs/reference/api/documents/delete-documents-by-filter) field.
## `missing_document_id`
A document does not contain any value for the required primary key, and is thus invalid. Check documents in the current addition for the invalid ones.
## `missing_index_uid`
The payload is missing the [`uid`](/docs/reference/api/indexes/get-index) field.
## `missing_facet_search_facet_name`
The [`facetName`](/docs/reference/api/facet-search/search-for-facet-values) parameter is required.
## `missing_master_key`
You need to set a master key before you can access the `/keys` route. Read more about setting a master key at launch in our [security tutorial](/docs/resources/self_hosting/security/basic_security).
## `missing_network_url`
One of the remotes in the [network object](/docs/reference/api/network/get-network) does not contain the `url` field.
## `missing_payload`
The Content-Type header was specified, but no request body was sent to the server or the request body is empty.
## `missing_swap_indexes`
The index swap payload is missing the [`indexes`](/docs/reference/api/indexes/swap-indexes) object.
## `missing_task_filters`
The [cancel tasks](/docs/reference/api/tasks/cancel-tasks) and [delete tasks](/docs/reference/api/tasks/delete-tasks) endpoints require one of the available query parameters.
## `no_space_left_on_device`
This error occurs if:
* The host system partition reaches its maximum capacity and can no longer accept writes
* The tasks queue reaches its limit and can no longer accept writes. You can delete tasks using the [delete tasks endpoint](/docs/reference/api/tasks/delete-tasks) to continue write operations
## `not_found`
The requested resources could not be found.
## `payload_too_large`
The payload sent to the server was too large. Check out this [guide](/docs/resources/self_hosting/configuration/reference#payload-limit-size) to customize the maximum payload size accepted by Meilisearch.
## `task_not_found`
The requested task does not exist. Please ensure that you are using the correct [`uid`](/docs/reference/api/tasks/get-task).
## `too_many_open_files`
Indexing a large batch of documents, such as a JSON file over 3.5GB in size, can result in Meilisearch opening too many file descriptors. Depending on your machine, this might reach your system's default resource usage limits and trigger the `too_many_open_files` error. Use [`ulimit`](https://www.ibm.com/docs/en/aix/7.1?topic=u-ulimit-command) or a similar tool to increase resource consumption limits before running Meilisearch. For example, call `ulimit -Sn 3000` in a UNIX environment to raise the number of allowed open file descriptors to 3000.
## `too_many_search_requests`
You have reached the limit of concurrent search requests. You may configure it by relaunching your instance and setting a higher value to [`--experimental-search-queue-size`](/docs/resources/self_hosting/configuration/overview).
## `unretrievable_document`
The document exists in store, but there was an error retrieving it. This probably comes from an inconsistent state in the database.
## `vector_embedding_error`
Error while generating embeddings. Common causes include:
* **Provider unavailability**: The embedding provider service is temporarily down or unreachable. Most providers offer status pages to monitor the state of their services, such as OpenAI's [https://status.openai.com/](https://status.openai.com/). Errors of this type usually include a message stating Meilisearch "could not reach embedding server".
* **Invalid or expired API key**: The API key configured for your external embedding provider (OpenAI, Cohere, etc.) is incorrect, expired, or has exceeded its rate limit. Verify your key is valid and has sufficient quota.
* **Misconfigured embedder settings**: The [`embedders`](/docs/reference/api/settings/get-embedders) index setting contains incorrect values, such as a wrong model name, an invalid URL for a REST embedder, or missing required fields.
* **Dimension mismatch**: The dimensions of the vectors provided or generated do not match the dimensions expected by the embedder configuration. Ensure the `dimensions` value in your embedder settings matches the output of your embedding model.
* **Input too large**: The document content sent to the embedding provider exceeds the model's maximum token or input length. Consider reducing the size of your [`documentTemplate`](/docs/capabilities/hybrid_search/advanced/document_template_best_practices) or limiting the attributes included in it.
## `remote_bad_response`
The remote instance answered with a response that this instance could not use as a federated search response.
## `remote_bad_request`
The remote instance answered with `400 BAD REQUEST`.
## `remote_could_not_send_request`
There was an error while sending the remote federated search request.
## `remote_invalid_api_key`
The remote instance answered with `403 FORBIDDEN` or `401 UNAUTHORIZED` to this instance’s request. The configured search API key is either missing, invalid, or lacks the required search permission.
## `remote_remote_error`
The remote instance answered with `500 INTERNAL ERROR`.
## `remote_timeout`
The proxy did not answer in the allocated time.
## `webhook_not_found`
The provided webhook `uuid` does not correspond to any configured webhooks in the instance.
# Errors
Source: https://www.meilisearch.com/docs/reference/errors/overview
Consult this page for an overview of how Meilisearch reports and formats error objects.
Meilisearch uses the following standard HTTP codes for a successful or failed API request:
| Status code | Description |
| :---------- | :---------------------------------------------------------------------------------------- |
| 200 | ✅ **Ok** Everything worked as expected. |
| 201 | ✅ **Created** The resource has been created (synchronous) |
| 202 | ✅ **Accepted** The task has been added to the queue (asynchronous) |
| 204 | ✅ **No Content** The resource has been deleted or no content has been returned |
| 205 | ✅ **Reset Content** All the resources have been deleted |
| 400 | ❌ **Bad Request** The request was unacceptable, often due to missing a required parameter |
| 401 | ❌ **Unauthorized** No valid API key provided |
| 403 | ❌ **Forbidden** The API key doesn't have the permissions to perform the request |
| 404 | ❌ **Not Found** The requested resource doesn't exist |
## Errors
All detailed task responses contain an [`error`](/docs/reference/api/tasks/get-task) field. When a task fails, it is always accompanied by a JSON-formatted error response. Meilisearch errors can be of one of the following types:
| Type | Description |
| :-------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`invalid_request`** | This is due to an error in the user input. It is accompanied by the HTTP code `4xx` |
| **`internal`** | This is due to machine or configuration constraints. It is accompanied by the HTTP code `5xx` |
| **`auth`** | This type of error is related to authentication and authorization. It is accompanied by the HTTP code `4xx` |
| **`system`** | This indicates your system has reached or exceeded its limit for disk size, index size, open files, or the database doesn't have read or write access. It is accompanied by the HTTP code `5xx` |
### Error format
```json theme={null}
{
"message": "Index `movies` not found.",
"code": "index_not_found",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#index_not_found"
}
```
| Field | Description |
| :------------ | :------------------------------------------------ |
| **`message`** | Human-readable description of the error |
| **`code`** | [Error code](/docs/reference/errors/error_codes) |
| **`type`** | [Type](#errors) of error returned |
| **`link`** | Link to the relevant section of the documentation |
If you're having trouble understanding an error, take a look at the [complete list](/docs/reference/errors/error_codes) of `code` values and descriptions.
TEST RESPONSE FIELD COMPONENT
Human-readable description of the error
[Error code](/docs/reference/errors/error_codes)
[Type](#errors) of error returned
Link to the relevant section of the documentation
# Meilisearch vs Algolia
Source: https://www.meilisearch.com/docs/resources/comparisons/algolia
Compare Meilisearch and Algolia to find the right search-as-a-service solution. Learn about pricing, features, and when each makes sense.
Algolia is a hosted search-as-a-service platform founded in 2012, powering over 1.75 trillion searches annually. Known for lightning-fast results and sophisticated AI features, Algolia has become a go-to choice for enterprises seeking premium search experiences.
## Quick comparison
| | Meilisearch | Algolia |
| -------------------- | :------------------------------------: | :------------------------------------------------: |
| **Primary focus** | Developer-friendly search | Enterprise search-as-a-service |
| **Open source** | Yes (MIT CE / BUSL-1.1 EE) | No (closed source) |
| **Self-hosting** | Yes | No |
| **Setup complexity** | Minimal | Low to moderate |
| **AI search** | Hybrid search (all plans) | NeuralSearch (premium only) |
| **Merchandising** | Search Rules with visual editor | Full dashboard, A/B testing, platform integrations |
| **Pricing model** | Fixed monthly tiers | Usage-based (records + searches) |
| **Starting price** | Free (self-hosted), \$30/month (cloud) | Free tier, then usage-based |
## What Algolia does well
### Mature e-commerce platform
Algolia offers a dedicated merchandising dashboard for managing search promotions at scale, A/B testing for comparing search configurations with statistical analysis, and pre-built connectors for major e-commerce platforms including Magento, Salesforce Commerce Cloud, and Shopify. These connectors let non-technical teams configure search without writing code, which matters when there is no dedicated developer on the project.
### Global infrastructure
With 16 server regions worldwide, Algolia's Distributed Search Network ensures low latency globally. This extensive coverage is beneficial for businesses operating internationally.
### Advanced analytics
Algolia provides comprehensive analytics including click-through rates, conversion tracking, and revenue attribution. The A/B testing feature allows comparing different search configurations with statistical analysis.
### AI capabilities
NeuralSearch combines traditional keyword search with semantic vector search. Dynamic Re-Ranking automatically adjusts results based on user behavior patterns.
## When to choose Meilisearch instead
### You value open-source flexibility
Meilisearch's Community Edition is fully open-source under the MIT license. You can inspect the code, contribute improvements, self-host without limitations, and avoid vendor lock-in. Algolia is entirely closed-source.
### You need predictable pricing
Algolia's usage-based model can lead to unexpected costs as you scale. Meilisearch Cloud offers plans starting at \$30/month, making budgeting straightforward.
### You want AI search without premium tiers
Meilisearch's hybrid search combining keyword and semantic search is available on all plans and for self-hosted deployments. Algolia restricts NeuralSearch to its highest-priced Elevate plan.
### You prefer self-hosting
Meilisearch can be self-hosted for free with full feature access. Algolia has no self-hosting option, meaning you're entirely dependent on their infrastructure and pricing decisions.
### You need simpler setup
While both platforms work well out-of-the-box, Meilisearch's API is designed for maximum simplicity. Features like sorting don't require index replication, and configuration has fewer moving parts.
### Budget is a concern
Algolia's pricing can become expensive for small-to-medium businesses. Their premium features and complex pricing model (records, searches, API operations) can lead to higher total costs than anticipated.
### You need search merchandising and result curation
Meilisearch Search Rules let you pin specific documents to fixed positions in search results, triggered by query keywords, empty-query states, or time windows. Rules are created and managed through a visual editor in the Meilisearch Cloud dashboard. This covers the common merchandising scenarios: promoting a seasonal product during a campaign, curating what users see when they open search with no query, or surfacing a specific page when a known keyword appears.
## When to choose Algolia
Consider Algolia if:
* You need a full merchandising dashboard with A/B testing for search configurations
* You require 16+ global server regions for international deployments
* You have the budget for premium features and usage-based pricing
* Your team has no dedicated developers and needs out-of-the-box connectors for platforms such as Magento, Salesforce Commerce Cloud, or Shopify
* Your team can dedicate resources to implementation and optimization
## Migration resources
If you're switching from Algolia to Meilisearch:
* [Algolia migration guide](/docs/resources/migration/algolia_migration) - Step-by-step migration instructions
* [InstantSearch integration](/docs/getting_started/instant_meilisearch/javascript) - Use the same frontend libraries
* [Pricing comparison](https://www.meilisearch.com/pricing) - Compare costs for your use case
Algolia is a registered trademark of Algolia, Inc. This comparison is based on publicly available information and our own analysis.
# Comparison to alternatives
Source: https://www.meilisearch.com/docs/resources/comparisons/alternatives
Deciding on a search engine for your project is an important but difficult task. This article describes the differences between Meilisearch and other search engines.
There are many search engines on the web, both open-source and otherwise. Deciding which search solution is the best fit for your project is very important, but also difficult. In this article, we'll go over the differences between Meilisearch and other search engines:
* In the [comparison table](#comparison-table), we present a general overview of the differences between Meilisearch and other search engines
* In the [approach comparison](#approach-comparison), instead, we focus on how Meilisearch measures up against [Elasticsearch](#meilisearch-vs-elasticsearch) and [Algolia](#meilisearch-vs-algolia), currently two of the biggest solutions available in the market
* Finally, we end this article with [an in-depth analysis of the broader search engine landscape](#a-quick-look-at-the-search-engine-landscape)
Please be advised that many of the search products described below are constantly evolving, just like Meilisearch. These are only our own impressions, and may not reflect recent changes. If something appears inaccurate, please don't hesitate to open an [issue or pull request](https://github.com/meilisearch/documentation).
## Detailed comparisons
For in-depth comparisons with specific alternatives, see our dedicated guides:
Full-text search and analytics engine
Enterprise search-as-a-service
Open-source instant search
Database full-text search
Managed vector database
Open-source vector database
AWS-backed Elasticsearch fork
MongoDB Atlas Search
## Comparison table
### General overview
| | Meilisearch | Algolia | Typesense | Elasticsearch |
| --------------------- | :--------------------------------------------------------------------------------------------------: | :------------: | :------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------: |
| Source code licensing | [MIT](https://choosealicense.com/licenses/mit/) (CE) / [BUSL-1.1](https://mariadb.com/bsl11/) (EE) | Closed-source | [GPL-3](https://choosealicense.com/licenses/gpl-3.0/)
(Fully open-source) | [AGPLv3](https://choosealicense.com/licenses/agpl-3.0/) / SSPL / ELv2
(open-source) |
| Built with | Rust
[Check out why we believe in Rust](https://www.abetterinternet.org/docs/memory-safety/). | C++ | C++ | Java |
| Data storage | Disk with Memory Mapping -- Not limited by RAM | Limited by RAM | Limited by RAM | Disk with RAM cache |
### Features
#### Integrations and SDKs
Note: we are only listing libraries officially supported by the internal teams of each different search engine.
Can't find a client you'd like us to support? [Submit your idea here](https://github.com/orgs/meilisearch/discussions)
| SDK | Meilisearch | Algolia | Typesense | Elasticsearch |
| ------------------------------------------------------------------------------------------------------------- | :---------: | :-----: | :-----------: | :---------------------------------------: |
| REST API | ✅ | ✅ | ✅ | ✅ |
| [JavaScript client](https://github.com/meilisearch/meilisearch-js) | ✅ | ✅ | ✅ | ✅ |
| [PHP client](https://github.com/meilisearch/meilisearch-php) | ✅ | ✅ | ✅ | ✅ |
| [Python client](https://github.com/meilisearch/meilisearch-python) | ✅ | ✅ | ✅ | ✅ |
| [Ruby client](https://github.com/meilisearch/meilisearch-ruby) | ✅ | ✅ | ✅ | ✅ |
| [Java client](https://github.com/meilisearch/meilisearch-java) | ✅ | ✅ | ✅ | ✅ |
| [Swift client](https://github.com/meilisearch/meilisearch-swift) | ✅ | ✅ | ✅ | ❌ |
| [.NET client](https://github.com/meilisearch/meilisearch-dotnet) | ✅ | ✅ | ✅ | ✅ |
| [Rust client](https://github.com/meilisearch/meilisearch-rust) | ✅ | ❌ | 🔶
WIP | ✅ |
| [Go client](https://github.com/meilisearch/meilisearch-go) | ✅ | ✅ | ✅ | ✅ |
| [Dart client](https://github.com/meilisearch/meilisearch-dart) | ✅ | ✅ | ✅ | ❌ |
| [Symfony](https://github.com/meilisearch/meilisearch-symfony) | ✅ | ✅ | ✅ | ❌ |
| Django | ❌ | ✅ | ❌ | ❌ |
| [Rails](https://github.com/meilisearch/meilisearch-rails) | ✅ | ✅ | 🔶
WIP | ✅ |
| [Official Laravel Scout Support](https://github.com/laravel/scout) | ✅ | ✅ | ✅ | ❌
Available as a standalone module |
| [Instantsearch](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/instant-meilisearch) | ✅ | ✅ | ✅ | ✅ |
| [Autocomplete](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/autocomplete-client) | ✅ | ✅ | ✅ | ✅ |
| [Docsearch](https://github.com/meilisearch/docs-scraper) | ✅ | ✅ | ✅ | ❌ |
| [Strapi](https://github.com/meilisearch/strapi-plugin-meilisearch) | ✅ | ✅ | ❌ | ❌ |
| [Gatsby](https://github.com/meilisearch/gatsby-plugin-meilisearch) | ✅ | ✅ | ✅ | ❌ |
| [Firebase](https://github.com/meilisearch/firestore-meilisearch) | ✅ | ✅ | ✅ | ❌ |
#### Configuration
##### Document schema
| | Meilisearch | Algolia | Typesense | Elasticsearch |
| ------------------------------- | :-----------------------: | :-----: | :------------------------------------------------------------------: | :---------------------: |
| Schemaless | ✅ | ✅ | 🔶
`id` field is required and must be a string | ✅ |
| Nested field support | ✅ | ✅ | ✅ | ✅ |
| Nested document querying | ❌ | ❌ | ❌ | ✅ |
| Automatic document ID detection | ✅ | ❌ | ❌ | ❌ |
| Native document formats | `JSON`, `NDJSON`, `CSV` | `JSON` | `NDJSON` | `JSON`, `NDJSON`, `CSV` |
| Compression Support | Gzip, Deflate, and Brotli | Gzip | ❌
Reads payload as JSON which can lead to document corruption | Gzip |
##### Relevancy
| | Meilisearch | Algolia | Typesense | Elasticsearch |
| ---------------------------- | :---------: | :-----: | :------------------------------------------------------------------------------: | :---------------------------------------------: |
| Typo tolerant | ✅ | ✅ | ✅ | 🔶
Needs to be specified by fuzzy queries |
| Orderable ranking rules | ✅ | ✅ | 🔶
Field weight can be changed, but ranking rules order cannot be changed. | ❌ |
| Custom ranking rules | ✅ | ✅ | ✅ | 🔶
Function score query |
| Query field weights | ✅ | ✅ | ✅ | ✅ |
| Synonyms | ✅ | ✅ | ✅ | ✅ |
| Stop words | ✅ | ✅ | ✅ | ✅ |
| Automatic language detection | ✅ | ✅ | ❌ | ❌ |
| All language supports | ✅ | ✅ | ✅ | ✅ |
| Ranking Score Details | ✅ | ✅ | 🔶
`_text_match_info` | ✅ |
##### Security
| | Meilisearch | Algolia | Typesense | Elasticsearch |
| ------------------------------------ | :--------------------------------------------------------------: | :-----: | :-------: | :-----------------: |
| API Key Management | ✅ | ✅ | ✅ | ✅ |
| Tenant tokens & multi-tenant indexes | ✅
[Multitenancy support](/docs/capabilities/security/overview) | ✅ | ✅ | ✅
Role-based |
##### Search
| | Meilisearch | Algolia | Typesense | Elasticsearch |
| ---------------------------------------------------------------------------- | :--------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | :-----------: |
| Placeholder search | ✅ | ✅ | ✅ | ✅ |
| Multi-index search | ✅ | ✅ | ✅ | ✅ |
| Federated search | ✅ | 🔶
Multi-query returns separate result sets, not a merged ranked list | ❌ | ✅ |
| Exact phrase search | ✅ | ✅ | ✅ | ✅ |
| Geo search | ✅ | ✅ | ✅ | ✅ |
| Sort by | ✅ | 🔶
Limited to one `sort_by` rule per index. Indexes may have to be duplicated for each sort field and sort order | ✅
Up to 3 sort fields per search query | ✅ |
| Filtering | ✅
Support complex filter queries with an SQL-like syntax. | ✅
Supports complex filters with disjunctive facets | ✅ | ✅ |
| Faceted search | ✅ | ✅ | ✅
Faceted fields must be searchable
Faceting can take several seconds when >10 million facet values must be returned | ✅ |
| Merchandising / Result curation | ✅
Search Rules with visual editor | ✅
Full dashboard, A/B testing, platform integrations | ❌ | ❌ |
| Distinct attributes
De-duplicate documents by a field value
| ✅ | ✅ | ✅ | ✅ |
| Grouping
Bucket documents by field values
| 🔶
Via `distinct` parameter | ✅ | ✅ | ✅ |
##### AI-powered search
| | Meilisearch | Algolia | Typesense | Elasticsearch |
| --------------------- | :----------------------------------------------------------------------------: | :----------------------------------: | :----------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------: |
| Semantic Search | ✅ | 🔶
NeuralSearch, Elevate plan | ✅ | ✅ |
| Hybrid Search | ✅ | 🔶
NeuralSearch, Elevate plan | ✅ | ✅ |
| Embedding Generation | ✅
OpenAI
HuggingFace
Ollama
REST embedders
| Undisclosed | ✅
Built-in ONNX models
OpenAI
Azure OpenAI
GCP Vertex AI | ✅
ELSER
E5
Cohere
OpenAI
Azure
Google AI Studio
Hugging Face
|
| Prompt Templates | ✅ | Undisclosed | ❌ | ❌ |
| Vector Store | ✅
Built-in DiskANN | Undisclosed | ✅ | ✅ |
| Langchain Integration | ✅ | ❌ | ✅ | ✅ |
| GPU support | ✅
CUDA | Undisclosed | ✅
CUDA | ✅
Elastic Inference Service |
##### Visualize
| | Meilisearch | Algolia | Typesense | Elasticsearch |
| --------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------: | :---------------------: | :------------------------------------------: | :--------------------: |
| [Mini Dashboard](https://github.com/meilisearch/mini-dashboard) | ✅ | 🔶
Cloud product | 🔶
Cloud product | ✅ |
| Search Analytics | ✅
[Cloud product](https://www.meilisearch.com/cloud) | ✅
Cloud Product | ✅
Query tracking, clicks, conversions | ✅
Cloud Product |
| Monitoring Dashboard | ✅
[Cloud product](/docs/capabilities/analytics/getting_started)
[Prometheus metrics endpoint](/docs/reference/api/metrics) for Grafana | ✅
Cloud Product | ✅
Cloud Product | ✅
Cloud Product |
#### Deployment
| | Meilisearch | Algolia | Typesense | Elasticsearch |
| ------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----: | :-----------------------------------------------------: | :---------------------------------------------------------------------: |
| Self-hosted | ✅ | ❌ | ✅ | ✅ |
| Platform Support | ARM
x86
x64 | n/a | 🔶 ARM (requires Docker on macOS)
x86
x64 | ARM
x86
x64 |
| Official 1-click deploy | ✅
[DigitalOcean](https://marketplace.digitalocean.com/apps/meilisearch)
[Platform.sh](https://console.platform.sh/projects/create-project?template=https://raw.githubusercontent.com/platformsh/template-builder/master/templates/meilisearch/.platform.template.yaml)
[Azure](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fcmaneu%2Fmeilisearch-on-azure%2Fmain%2Fmain.json)
[Railway](https://railway.app/new/template/TXxa09?referralCode=YltNo3)
[Koyeb](https://app.koyeb.com/deploy?type=docker\&image=getmeili/meilisearch\&name=meilisearch-on-koyeb\&ports=7700;http;/\&env%5BMEILI_MASTER_KEY%5D=REPLACE_ME_WITH_A_STRONG_KEY) | ❌ | ✅
DigitalOcean, AWS, GCP Marketplace | ❌ |
| Official cloud-hosted solution | [Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=comparison-table) | ✅ | ✅ | ✅ |
| High availability | ✅
Sharding & replication (Cloud and self-hosted) | ✅ | ✅ | ✅ |
| Run-time dependencies | None | N/A | None | None |
| Backward compatibility | ✅ | N/A | ✅ | ✅ |
| Upgrade path | Only changed data is reindexed on upgrade | N/A | Documents are automatically reindexed on upgrade | Documents are automatically reindexed on upgrade, up to 1 major version |
| Boot time | Instant | N/A | Loads index from disk to RAM on boot | Instant |
### Limits
| | Meilisearch | Algolia | Typesense | Elasticsearch |
| ------------------------- | :-----------: | :---------------------------------------------------: | :----------------: | :-------------------------: |
| Maximum number of indexes | No limitation | 1000, increasing limit possible by contacting support | No limitation | No limitation |
| Maximum index size | 80TiB | 100GB (plan-dependent) | Constrained by RAM | No limitation |
| Maximum document size | No limitation | 100KB, configurable | No limitation | 100KB default, configurable |
### Community
| | Meilisearch | Algolia | Typesense | Elasticsearch |
| ------------------------------------------ | :---------: | :-----: | :-------: | :-----------: |
| GitHub stars of the main project | 56K | N/A | 25K | 76K |
| Number of contributors on the main project | 200+ | N/A | 100+ | 1,900+ |
| Public Discord/Slack community size | 3,000+ | N/A | 2,000 | 16K |
### Support
| | Meilisearch | Algolia | Typesense | Elasticsearch |
| --------------------- | :-------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------: | :--------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------: |
| Status page | ✅ | ✅ | ✅ | ✅ |
| Free support channels | Instant messaging / chatbox (2-3h delay),
emails,
public Discord community,
GitHub issues & discussions | Instant messaging / chatbox,
public community forum | Instant messaging/chatbox (24h-48h delay),
public Slack community,
GitHub issues. | Public Slack community,
public community forum,
GitHub issues |
| Paid support channels | Slack Channel, emails, personalized support (whatever you need, we'll be there!) | Emails | Emails,
phone,
private Slack | Web support,
emails,
phone |
## Approach comparison
### Meilisearch vs Elasticsearch
Elasticsearch is designed as a backend search engine. Although it is not suited for this purpose, it is commonly used to build search bars for end-users.
Elasticsearch can handle searching through massive amounts of data and performing text analysis. In order to make it effective for end-user searching, you need to spend time understanding more about how Elasticsearch works internally to be able to customize and tailor it to fit your needs.
Unlike Elasticsearch, which is a general search engine designed for large amounts of log data (for example, back-facing search), Meilisearch is intended to deliver performant instant-search experiences aimed at end-users (for example, front-facing search).
Elasticsearch can sometimes be too slow if you want to provide a full instant search experience. Most of the time, it is significantly slower in returning search results compared to Meilisearch.
Meilisearch is a perfect choice if you need a simple and easy tool to deploy a typo-tolerant search bar. It provides prefix searching capability, makes search intuitive for users, and returns results instantly with excellent relevance out of the box.
For a more detailed analysis of how it compares with Meilisearch, refer to our [blog post on Elasticsearch](https://blog.meilisearch.com/meilisearch-vs-elasticsearch/?utm_campaign=oss\&utm_source=docs\&utm_medium=comparison).
### Meilisearch vs Algolia
Meilisearch and Algolia solve a similar problem: fast, relevant, typo-tolerant search for end users. Algolia focuses primarily on ecommerce, marketplaces, and retail, with a merchandising toolset built for those use cases. Meilisearch supports these as well as others, including SaaS and enterprise applications, media and content discovery, and AI-driven experiences.
Meilisearch is a flexible search engine, written in Rust and built on modern information retrieval research to deliver relevance and speed out of the box. It is AI-native, with hybrid semantic search, built-in vector storage, and agentic retrieval for RAG and AI applications. It is model-agnostic: embeddings and LLMs from providers such as OpenAI, Hugging Face, or Ollama connect through REST embedders and can be swapped as the ecosystem evolves.
The fastest way to get started is [Meilisearch Cloud](https://www.meilisearch.com/cloud), a fully managed service with a 14-day free trial. Meilisearch is also open-source and can be self-hosted. Current Algolia users can refer to the [migration guide](/docs/resources/migration/algolia_migration).
#### Key similarities
Some of the most significant similarities between Algolia and Meilisearch are:
* [Features](/docs/getting_started/overview) such as search-as-you-type, typo tolerance, faceting, etc.
* Fast results targeting an instant search experience (answers \< 50 milliseconds)
* Schemaless indexing
* Support for all JSON data types
* Asynchronous API
* Similar query response
#### Key differences
* Meilisearch is available as [Meilisearch Cloud](https://www.meilisearch.com/cloud), a fully managed service, and is also open-source for self-hosting. Algolia is closed-source and cloud-only.
* Meilisearch supports a range of use cases, including ecommerce, site search, SaaS, media, and AI applications.
* Meilisearch is AI-native, with hybrid semantic search, built-in vector storage, and agentic retrieval for RAG and AI applications.
* Model-agnostic embeddings and LLMs: OpenAI, Hugging Face, Ollama, or any provider connect through REST embedders.
* Written in Rust for speed, portability, and a low deployment footprint.
#### Pricing
Algolia's pricing is based on the number of records stored and the number of API operations performed. Meilisearch is available through [Meilisearch Cloud](https://www.meilisearch.com/cloud), a fully managed service starting at \$20/month with a 14-day free trial, offering usage-based or resource-based billing. An Enterprise plan adds dedicated infrastructure, custom SLAs, and enterprise compliance (SSO/SAML, SOC 2) for mission-critical deployments. For teams that prefer to manage their own infrastructure, Meilisearch is open-source and can be self-hosted.
## A quick look at the search engine landscape
### Open source
#### Lucene
Apache Lucene is a free and open-source search library used for indexing and searching full-text documents. It was created in 1999 by Doug Cutting, who had previously written search engines at Xerox's Palo Alto Research Center (PARC) and Apple. Written in Java, Lucene was developed to build web search applications such as Google and DuckDuckGo, the last of which still uses Lucene for certain types of searches.
Lucene has since been divided into several projects:
* **Lucene itself**: the full-text search library.
* **Solr**: an enterprise search server with a powerful REST API.
* **Nutch**: an extensible and scalable web crawler relying on Apache Hadoop.
Since Lucene is the technology behind many open source or closed source search engines, it is considered as the reference search library.
#### Sonic
Sonic is a lightweight and schema-less search index server written in Rust. Sonic cannot be considered as an out-of-the-box solution, and compared to Meilisearch, it does not ensure relevancy ranking. Instead of storing documents, it comprises an inverted index with a Levenshtein automaton. This means any application querying Sonic has to retrieve the search results from an external database using the returned IDs and then apply some relevancy ranking.
Its ability to run on a few MBs of RAM makes it a minimalist and resource-efficient alternative to database tools that can be too heavyweight to scale.
#### Typesense
Like Meilisearch, Typesense is a lightweight open-source search engine optimized for speed. To better understand how it compares with Meilisearch, refer to our [blog post on Typesense](https://blog.meilisearch.com/meilisearch-vs-typesense/?utm_campaign=oss\&utm_source=docs\&utm_medium=comparison).
#### Lucene derivatives
#### Lucene-Solr
Solr is a subproject of Apache Lucene, created in 2004 by Yonik Seeley, and is today one of the most widely used search engines available worldwide. Solr is a search platform, written in Java, and built on top of Lucene. In other words, Solr is an HTTP wrapper around Lucene's Java API, meaning you can leverage all the features of Lucene by using it. In addition, Solr server is combined with Solr Cloud, providing distributed indexing and searching capabilities, thus ensuring high availability and scalability. Data is shared but also automatically replicated.
Furthermore, Solr is not only a search engine; it is often used as a document-structured NoSQL database. Documents are stored in collections, which can be comparable to tables in a relational database.
Due to its extensible plugin architecture and customizable features, Solr is a search engine with an endless number of use cases even though, since it can index and search documents and email attachments, it is specifically popular for enterprise search.
#### Bleve & Tantivy
Bleve and Tantivy are search engine projects, respectively written in Golang and Rust, inspired by Apache Lucene and its algorithms (for example, tf-idf, short for term frequency-inverse document frequency). Such as Lucene, both are libraries to be used for any search project; however they are not ready-to-use APIs.
### Open source (Elasticsearch)
#### Elasticsearch
Elasticsearch is a search engine based on the Lucene library and is most popular for full-text search. It provides a REST API accessed by JSON over HTTP. Since August 2024, Elasticsearch is available under a triple license (AGPLv3 / SSPL / ELv2), making it open source again. One of its key options, called index sharding, gives you the ability to divide indexes into physical spaces in order to increase performance and ensure high availability. Both Lucene and Elasticsearch have been designed for processing high-volume data streams, analyzing logs, and running complex queries. You can perform operations and analysis (for example, calculate the average age of all users named "Thomas") on documents that match a specified query.
Today, Lucene and Elasticsearch are dominant players in the search engine landscape. They both are solid solutions for a lot of different use cases in search, and also for building your own recommendation engine. They are good general products, but they require to be configured properly to get similar results to those of Meilisearch or Algolia.
### Closed source
#### Algolia
Algolia is a company providing a search engine on a SaaS model. Its software is closed source. In its early stages, Algolia offered mobile search engines that could be embedded in apps, facing the challenge of implementing the search algorithms from scratch. From the very beginning, the decision was made to build a search engine directly dedicated to the end-users, specifically, implementing search within mobile apps or websites.
Algolia successfully demonstrated over the past few years how critical tolerating typos was in order to improve the users' experience, and in the same way, its impact on reducing bounce rate and increasing conversion.
Apart from Algolia, a wide choice of SaaS products are available on the Search Engine Market. Most of them use Elasticsearch and fine-tune its settings in order to have a custom and personalized solution.
#### Swiftype
Swiftype is a search service provider specialized in website search and analytics. Swiftype was founded in 2012 by Matt Riley and Quin Hoxie, and is now owned by Elastic since November 2017. It is an end-to-end solution built on top of Elasticsearch, meaning it has the ability to leverage the Elastic Stack.
#### Doofinder
Doofinder is a paid on-site search service that is developed to integrate into any website with very little configuration. Doofinder is used by online stores to increase their sales, aiming to facilitate the purchase process.
## Conclusions
Each Search solution fits best with the constraints of a particular use case. Since each type of search engine offers a unique set of features, it wouldn't be easy nor relevant to compare their performance. For instance, it wouldn't be fair to make a comparison of speed between Elasticsearch and Algolia over a product-based database. The same goes for a very large full text-based database.
We cannot, therefore, compare ourselves with Lucene-based or other search engines targeted to specific tasks.
In the particular use case we cover, the most similar solution to Meilisearch is Algolia.
Algolia offers a mature, polished product focused on ecommerce and retail search. Meilisearch delivers comparable instant-search quality across a range of use cases, as an AI-native, model-agnostic engine available fully managed on Meilisearch Cloud or self-hosted.
Meilisearch is dedicated to all types of developers. Our goal is to deliver a developer-friendly tool, easy to install, and to deploy. Because providing an out-of-the-box awesome search experience for the end-users matters to us, we want to give everyone access to the best search experiences out there with minimum effort and without requiring any financial resources.
Usually, when a developer is looking for a search tool to integrate into their application, they will go for Elasticsearch or less effective choices. Even if Elasticsearch is not best suited for this use case, it remains a great source available solution. However, it requires technical know-how to execute advanced features and hence more time to customize it to your business.
We aim to become the default solution for developers.
# Meilisearch vs Elasticsearch
Source: https://www.meilisearch.com/docs/resources/comparisons/elasticsearch
Compare Meilisearch and Elasticsearch to find the right search solution for your project. Learn key differences in setup, performance, and use cases.
Elasticsearch is a distributed search and analytics engine built on Apache Lucene, created by Shay Banon and first released in 2010. It has become the dominant force in enterprise search, powering everything from website search to log analytics for organizations worldwide.
## Quick comparison
| | Meilisearch | Elasticsearch |
| -------------------- | :------------------------------: | :--------------------------: |
| **Primary focus** | Fast, relevant search | Full-text search & analytics |
| **Setup complexity** | Ready in minutes | Steep learning curve |
| **Performance** | Under 50ms out-of-the-box | Fast with proper tuning |
| **Resource usage** | Lightweight | Memory-intensive |
| **Pricing** | Free OSS, affordable cloud plans | Free OSS, paid cloud tiers |
| **Open source** | MIT (CE) / BUSL-1.1 (EE) | AGPLv3 / SSPL / ELv2 |
| **Best for** | App/site search | Large-scale analytics |
## What Elasticsearch does well
### Massive scalability
Elasticsearch's distributed architecture can scale horizontally across hundreds of nodes, handling petabytes of data. Its shard-based design enables deployment across clusters of any size, making it suitable for organizations with massive datasets.
### Comprehensive analytics
The aggregations framework enables complex real-time analytics beyond simple search. You can compute metrics, create buckets for grouping data, and build pipeline aggregations. This supports use cases from dashboards to machine learning jobs.
### Elastic Stack ecosystem
Elasticsearch integrates with Kibana for visualization, Logstash and Beats for data ingestion, creating a complete observability platform. With over 350 integrations, it can connect to virtually any data source.
### Versatility
Elasticsearch handles multiple use cases: full-text search, log analytics, security monitoring, and application performance management. Its Query DSL offers extensive control over text analysis and searching.
## When to choose Meilisearch instead
### You need search that works immediately
Meilisearch delivers relevant, typo-tolerant search results out-of-the-box without configuration. With Elasticsearch, achieving similar relevancy requires understanding analyzers, mapping types, and the `fuzziness` parameter, along with significant tuning.
### You want minimal operational overhead
Elasticsearch cluster management requires expertise in shards, replicas, heap sizing, and index lifecycle management. Meilisearch now supports sharding and replication, but can also run as a single binary with no external dependencies, dramatically reducing operational complexity for getting started.
### Your team lacks dedicated search expertise
Elasticsearch's Query DSL has a steep learning curve. Simple tasks often require understanding multiple interconnected systems. Meilisearch's intuitive REST API can be learned in hours, not months.
### You need predictable costs
Elasticsearch's resource requirements can lead to infrastructure costs of thousands per month for production workloads. Meilisearch's efficient architecture reduces hosting costs significantly.
### You want simpler scaling
Meilisearch now supports sharding and replication while remaining simpler to operate than Elasticsearch. For most application search use cases, Meilisearch delivers consistent sub-50ms response times without the operational overhead of Elasticsearch clusters.
## When to choose Elasticsearch
Consider Elasticsearch if:
* You need to search and analyze multiple data types (logs, metrics, documents) in a unified platform
* Your dataset exceeds billions of documents
* You have a dedicated operations team with Elasticsearch expertise
* You need advanced aggregations and analytics beyond search
* You're building observability, security monitoring, or log analytics solutions
* You require fine-grained control over every aspect of text analysis
## Migration resources
If you're considering switching from Elasticsearch to Meilisearch:
* [Migrating from Elasticsearch](/docs/resources/migration/elasticsearch_migration) - Step-by-step data export and import guide with query and settings comparison
* [Meilisearch quick start](/docs/getting_started/first_project) - Get up and running in minutes
* [Indexing documents](/docs/resources/internals/documents) - Learn how to import your data
Elasticsearch is a registered trademark of Elastic N.V. This comparison is based on publicly available information and our own analysis.
# Meilisearch vs MongoDB Atlas Search
Source: https://www.meilisearch.com/docs/resources/comparisons/mongodb
Compare Meilisearch with MongoDB Atlas Search. Learn when a dedicated search engine outperforms database-integrated search.
MongoDB Atlas Search integrates Apache Lucene-based full-text search directly into MongoDB Atlas. It allows searching MongoDB collections without a separate search infrastructure, using familiar MongoDB Query API syntax.
## Quick comparison
| | Meilisearch | MongoDB Atlas Search |
| ---------------------- | :------------------------: | :-------------------------------------------------: |
| **Primary purpose** | Search engine | Database with search |
| **Typo tolerance** | Built-in | Via fuzzy matching config |
| **Search-as-you-type** | Optimized (under 50ms) | Possible but not optimized |
| **Self-hosting** | Yes | Atlas (managed) or Community Edition 8.2+ (preview) |
| **Faceted search** | Native, optimized | Via aggregation pipeline |
| **Relevancy tuning** | Configurable ranking rules | Score modifiers |
| **Frontend libraries** | InstantSearch compatible | None |
## What MongoDB Atlas Search does well
### Unified data platform
Atlas Search eliminates the need to synchronize data between MongoDB and a separate search engine. Your search index stays automatically in sync with your documents.
### Familiar syntax
If you're already using MongoDB, Atlas Search uses the same aggregation pipeline syntax. No new query language to learn.
### Vector search support
Atlas Vector Search enables semantic search and RAG applications using vector embeddings alongside traditional search. MongoDB also offers Automated Embedding with Voyage AI integration, generating embeddings natively on insert, update, and query.
### Managed infrastructure
As part of Atlas, search infrastructure is fully managed with automatic scaling, backups, and monitoring.
## When to choose Meilisearch instead
### You need instant search-as-you-type
Meilisearch is architected for sub-50ms response times, essential for search-as-you-type experiences. Atlas Search, while capable, isn't optimized specifically for this use case.
### Typo tolerance is critical
Meilisearch handles typos automatically with configurable tolerance per attribute. Atlas Search requires explicit fuzzy matching configuration and doesn't provide the same level of automatic typo handling.
### You want better relevancy out-of-the-box
Meilisearch's ranking rules provide relevant results without configuration. Atlas Search requires more tuning through score modifiers to achieve similar relevancy.
### You need frontend integration
Meilisearch works with InstantSearch libraries, providing pre-built UI components for search bars, facets, and pagination. Atlas Search has no equivalent frontend ecosystem.
### Self-hosting flexibility
Meilisearch can be self-hosted anywhere with full feature access. MongoDB has extended search and vector search to Community Edition 8.2+ and Enterprise Server (public preview since September 2025), but these self-managed capabilities are still maturing compared to Atlas Search.
### You use a different database
If your primary database isn't MongoDB, adding Atlas Search isn't an option. Meilisearch works with any data source through its REST API.
### Faceted search performance matters
Meilisearch provides optimized APIs for facet filtering and counting. Atlas Search handles facets through aggregation pipelines, which can be less efficient for complex faceted navigation.
### You're not on Atlas
While MongoDB has extended search capabilities to self-managed deployments (Community Edition 8.2+, public preview), the most mature search experience remains on Atlas. If you're using an older self-hosted MongoDB version, search capabilities are limited.
## When to choose MongoDB Atlas Search
Consider Atlas Search if:
* You're already using MongoDB Atlas and want to minimize infrastructure
* Keeping search synchronized with your primary data is a priority
* Your team is deeply familiar with MongoDB aggregation pipelines
* Search requirements are moderate (not real-time, not highly tuned)
* You need vector search alongside your existing MongoDB documents
* Managed infrastructure is preferred over self-hosting
## Migration resources
If you're considering switching from MongoDB Atlas Search to Meilisearch:
* [Migrating from MongoDB Atlas Search](/docs/resources/migration/mongodb_migration) - Step-by-step data export and import guide with query and settings comparison
* [Quick start guide](/docs/getting_started/first_project) - Set up Meilisearch
* [Indexing documents](/docs/resources/internals/documents) - Import data from any source
MongoDB and MongoDB Atlas are registered trademarks of MongoDB, Inc. This comparison is based on publicly available information and our own analysis.
# Meilisearch vs OpenSearch
Source: https://www.meilisearch.com/docs/resources/comparisons/opensearch
Compare Meilisearch and OpenSearch for search and analytics. Learn the differences and when each solution makes sense.
OpenSearch is an open-source search and analytics suite derived from Elasticsearch 7.10.2. Created by AWS in 2021 after Elastic changed Elasticsearch's license, OpenSearch maintains compatibility with the Elasticsearch API while being fully open-source under Apache 2.0.
## Quick comparison
| | Meilisearch | OpenSearch |
| -------------------- | :-------------------------------------------------: | :-------------------------: |
| **Primary focus** | Fast, relevant search | Search & analytics platform |
| **License** | MIT (CE) / BUSL-1.1 (EE) | Apache 2.0 |
| **Setup complexity** | Ready in minutes | Steep learning curve |
| **Performance** | Under 50ms out-of-the-box | Requires tuning |
| **Resource usage** | Lightweight | Memory-intensive |
| **Architecture** | Single-node or distributed (sharding & replication) | Distributed clusters |
| **Best for** | App/site search | Large-scale analytics |
## What OpenSearch does well
### Truly open source
OpenSearch is fully open-source under Apache 2.0 with no proprietary components. Since September 2024, the project is governed by the OpenSearch Software Foundation under the Linux Foundation, ensuring vendor-neutral community governance.
### Elasticsearch compatibility
OpenSearch maintains API compatibility with Elasticsearch 7.x, making migration straightforward for existing Elasticsearch users. Most Elasticsearch tooling and knowledge transfers directly.
### Distributed architecture
Like Elasticsearch, OpenSearch scales horizontally across clusters for petabyte-scale deployments. The shard-based architecture supports massive data volumes.
### Analytics capabilities
OpenSearch includes dashboards (fork of Kibana), aggregations, and analytics features suitable for log analysis, observability, and business intelligence use cases.
### AWS integration
OpenSearch Service on AWS provides managed hosting with tight integration into the AWS ecosystem, including IAM, VPC, and CloudWatch.
## When to choose Meilisearch instead
### You need search that works immediately
Meilisearch delivers relevant, typo-tolerant search results without configuration. OpenSearch, like Elasticsearch, requires understanding analyzers, mappings, and query DSL to achieve similar relevancy.
### You want minimal operational overhead
OpenSearch cluster management requires expertise in shards, replicas, and distributed systems. Meilisearch now supports sharding and replication, but can also run as a single binary with no external dependencies, making it simpler to get started.
### Your team lacks search expertise
OpenSearch inherits Elasticsearch's complexity. The Query DSL has a steep learning curve, and optimal configuration requires significant experience. Meilisearch's intuitive API can be learned quickly.
### You need predictable resource usage
OpenSearch is memory-intensive and requires careful capacity planning. Meilisearch's efficient architecture provides consistent performance with lower resource requirements.
### You want simpler distributed search
Meilisearch now supports sharding and replication while remaining simpler to operate than OpenSearch. For most application search use cases, Meilisearch handles datasets with consistent sub-50ms responses without the operational overhead of OpenSearch clusters.
### You're building end-user search
OpenSearch is designed for backend search and analytics. Meilisearch is built specifically for user-facing instant search with features like typo tolerance and search-as-you-type.
## When to choose OpenSearch
Consider OpenSearch if:
* You're migrating from Elasticsearch and need API compatibility
* You need distributed search across petabytes of data
* You're building log analytics, observability, or SIEM solutions
* You require complex aggregations and analytics beyond search
* You want tight AWS integration through OpenSearch Service
* You have teams with existing Elasticsearch expertise
## Migration resources
If you're considering Meilisearch:
* [Quick start guide](/docs/getting_started/first_project) - Get running in minutes
* [Search preview](/docs/resources/self_hosting/getting_started/search_preview) - Explore capabilities
* [Indexing documents](/docs/resources/internals/documents) - Import your data
OpenSearch is a trademark of the OpenSearch project. This comparison is based on publicly available information and our own analysis.
# Meilisearch vs Pinecone
Source: https://www.meilisearch.com/docs/resources/comparisons/pinecone
Compare Meilisearch and Pinecone for AI-powered search. Learn when a vector database vs a hybrid search engine makes sense.
Pinecone is a fully managed vector database designed for AI applications, launched to make vector search accessible without complex infrastructure. It excels at storing and searching vector embeddings for semantic search and RAG (Retrieval-Augmented Generation) applications.
## Quick comparison
| | Meilisearch | Pinecone |
| ------------------------ | :------------------------------------------: | :-----------------------------: |
| **Primary focus** | Hybrid search | Vector database |
| **Full-text search** | Native, optimized | Via sparse vectors (limited) |
| **Open source** | Yes (MIT CE / BUSL-1.1 EE) | No (proprietary) |
| **Self-hosting** | Yes | No |
| **Embedding generation** | Built-in (OpenAI, HuggingFace, Ollama, REST) | Built-in (Integrated Inference) |
| **Starting price** | Free (self-hosted), \$30/month (cloud) | Free tier, then usage-based |
| **Typo tolerance** | Built-in | Not applicable |
## What Pinecone does well
### Purpose-built for vectors
Pinecone's architecture is optimized specifically for vector operations. Its HNSW algorithm delivers excellent similarity search performance at scale.
### Fully managed service
Pinecone abstracts away infrastructure complexity entirely. The serverless architecture handles scaling automatically without capacity planning.
### AI ecosystem integration
Pinecone offers Integrated Inference, allowing you to send raw text and have Pinecone handle embedding, storage, and retrieval in a single API call. It supports models like multilingual-e5-large and integrates smoothly with OpenAI and Cohere.
### Filtered vector search
Pinecone's approach integrates metadata filtering directly into the search process, enabling efficient combination of semantic similarity with business rules.
## When to choose Meilisearch instead
### You need both keyword and semantic search
Meilisearch's hybrid search combines traditional full-text search with vector search in a single query. Users get exact matches when they exist and semantically relevant results when they don't. Pinecone focuses primarily on vectors; its keyword capabilities via sparse vectors are limited compared to dedicated search engines.
### Typo tolerance matters
Meilisearch provides built-in typo tolerance that handles misspellings gracefully. Vector search alone doesn't handle typos in the same way, as embeddings are generated from the exact query text.
### You want open-source flexibility
Meilisearch's Community Edition is fully open-source under the MIT license. You can self-host, inspect the code, and avoid vendor lock-in. Pinecone is proprietary with no self-hosting option.
### You need predictable costs
Pinecone's usage-based pricing (per read/write unit + storage) can be unpredictable, with some users reporting unexpected charges from bandwidth and operation fees. Meilisearch Cloud offers plans starting at \$30/month.
### Full-text search is your primary need
If you're building traditional site search, e-commerce search, or documentation search where keyword matching is essential, Meilisearch's full-text capabilities are more mature than Pinecone's sparse vector approach.
### You want simpler architecture
Using Meilisearch for hybrid search means one system instead of running both a full-text search engine and a vector database. This reduces infrastructure complexity and data synchronization challenges.
## When to choose Pinecone
Consider Pinecone if:
* You're building AI-first applications where semantic search is the primary requirement
* You need a pure vector database for recommendation systems or similarity matching
* You prefer a fully managed service with zero infrastructure management
* Your team is deeply invested in AI/ML workflows and embedding pipelines
* You're implementing RAG for Large Language Models and need specialized tooling
* You can accept vendor lock-in for reduced operational overhead
## Migration resources
If you're evaluating Meilisearch for AI search:
* [AI-powered search guide](/docs/capabilities/hybrid_search/getting_started) - Set up hybrid search
* [Embedder configuration](/docs/capabilities/hybrid_search/how_to/choose_an_embedder) - Connect to embedding providers
* [Hybrid search](/docs/capabilities/hybrid_search/overview) - Understand the approach
Pinecone is a registered trademark of Pinecone Systems, Inc. This comparison is based on publicly available information and our own analysis.
# Meilisearch vs PostgreSQL search
Source: https://www.meilisearch.com/docs/resources/comparisons/postgresql
Compare Meilisearch with PostgreSQL's built-in full-text search and the pgvector extension. Learn when a dedicated search engine outperforms database search.
PostgreSQL includes built-in full-text search capabilities through its `tsvector` and `tsquery` data types, and the [pgvector](https://github.com/pgvector/pgvector) extension adds vector similarity search. While convenient for simple use cases, PostgreSQL's search falls short compared to dedicated search engines for user-facing applications.
This page compares Meilisearch with both approaches: PostgreSQL full-text search first, then [vector and hybrid search with pgvector](#vector-and-hybrid-search-pgvector).
## Quick comparison
| | Meilisearch | PostgreSQL FTS |
| ---------------------- | :-------------------------: | :---------------------------------------------------: |
| **Primary purpose** | Search engine | Relational database |
| **Typo tolerance** | Built-in | Requires `pg_trgm` extension |
| **Faceted search** | Native support | Complex to implement |
| **Language support** | CJK, Arabic, Hebrew + Latin | Limited (no native CJK; third-party extensions exist) |
| **Search-as-you-type** | Optimized for under 50ms | Not designed for this |
| **Relevancy tuning** | Configurable ranking rules | Basic `ts_rank` |
| **Frontend libraries** | InstantSearch compatible | None |
## What PostgreSQL FTS does well
### Single-system simplicity
Keeping search in your existing PostgreSQL database means no additional infrastructure to manage. For simple use cases, this reduces operational complexity.
### Transactional consistency
Search results are always consistent with your primary data, with no synchronization lag between database and search index.
### SQL integration
You can combine full-text search with regular SQL queries, joins, and aggregations in a single statement.
## When to choose Meilisearch instead
### You need typo tolerance
PostgreSQL's default full-text search cannot handle misspellings. The `pg_trgm` extension helps but doesn't provide true fuzzy matching with word proximity awareness. Meilisearch handles typos automatically with configurable tolerance per attribute.
### You want faceted search
Implementing faceted search in PostgreSQL is complex and resource-intensive, especially with multiple facet types and counts. Meilisearch provides optimized, first-class APIs for facet filtering and counting.
### You need instant search-as-you-type
PostgreSQL isn't optimized for the sub-50ms response times needed for search-as-you-type experiences. Full-text search queries on large datasets become costly, especially when ranking results.
### Your users speak non-Latin languages
PostgreSQL lacks dictionaries for Chinese, Japanese, Korean, and other languages requiring complex tokenization. Meilisearch provides optimized support for these languages with automatic detection.
### You want frontend integration
Search engines like Meilisearch work with InstantSearch libraries, providing pre-built UI components for search bars, facet filters, pagination, and more. PostgreSQL has no equivalent ecosystem.
### You need a public-facing search API
Meilisearch provides a secure REST API designed for public consumption with API key management and tenant tokens for multi-tenancy. Exposing PostgreSQL directly to clients creates security risks and requires building a custom API layer.
### Scaling is a concern
Full-text search queries on large PostgreSQL datasets compete for resources with your primary application workload. A dedicated search engine scales independently and uses data structures optimized for search operations.
### You want better relevancy
PostgreSQL's `ts_rank` only supports attribute weighting. Meilisearch offers configurable ranking rules for typo count, word proximity, exact matches, and custom business logic.
## Vector and hybrid search: pgvector
The pgvector extension adds a `vector` data type, distance operators, and optional approximate nearest neighbor (ANN) indexes to PostgreSQL. It is a storage and query layer, not a search engine: everything that turns raw vectors into a search experience (embedding generation, hybrid fusion, relevancy scoring, filtered vector search) is left for your application to build. Meilisearch ships all of it out of the box.
| | Meilisearch | pgvector |
| -------------------------- | :----------------------------------------------------: | :--------------------------------------------------: |
| **Embedding generation** | Built-in embedders (OpenAI, Cohere, Mistral, and more) | Application code |
| **Hybrid search** | Single query, fused inside the engine | Two queries, fused manually |
| **Score fusion** | Absolute relevancy scores (0 to 1) | Rank-based (RRF) |
| **Search type** | ANN (HNSW-based, via Hannoy) | Exact KNN by default, optional ANN (HNSW, IVFFlat) |
| **Filtered vector search** | Native, integrated with the index | Post-filtering on ANN indexes, recall can drop |
| **Quantization** | Binary quantization | `halfvec`, binary, sparse |
| **Dimension limits** | No documented limit | 2,000 (`vector`), 4,000 (`halfvec`), 64,000 (binary) |
### Embedding pipeline: built-in vs do-it-yourself
pgvector stores and queries vectors, but it never calls an embedding provider. Generating vectors is entirely your application's responsibility: calling the model API, batching, retries, API key management, and dimension mapping all happen in your code before insertion, and again at query time to embed the user's search terms. This is a permanent piece of infrastructure you have to build, monitor, and maintain.
With Meilisearch, you configure an [embedder declaratively](/docs/capabilities/hybrid_search/how_to/choose_an_embedder): OpenAI, Cohere, Mistral, Voyage, Jina, Hugging Face, Amazon Bedrock, Gemini, or any REST API. Meilisearch calls the model automatically at indexing time and at query time. There is no embedding pipeline to build, and switching models is a settings change, not a code rewrite.
### Hybrid search: one query vs two
With pgvector, hybrid search is not a single query. You run a full-text query and an ANN query separately, then merge the two result lists yourself, either in application code or with SQL CTEs, typically using Reciprocal Rank Fusion (RRF) or a cross-encoder. That fusion logic is search-engine internals you now own, test, and tune.
In Meilisearch, [hybrid search](/docs/capabilities/hybrid_search/overview) is a single query with a `hybrid: { semanticRatio, embedder }` parameter. The fusion of keyword and semantic results happens inside the engine, and one slider controls the balance between the two.
### Score fusion: RRF vs relevancy scores
pgvector's documentation recommends RRF to merge full-text and vector results. RRF combines documents based on their **rank** in each list, not their actual relevance. It assumes the top semantic result is as relevant as the top full-text result, which breaks down whenever one of the two methods performs poorly on a given query.
Meilisearch computes an **absolute relevancy score** between 0 and 1 for every document, comparable across both search methods. It also applies a correction for the typical compression of embedding similarity scores, which often cluster between 0.5 and 0.7 even for unrelated content. A weak semantic match cannot outrank a strong keyword match purely by position: documents win on actual relevance, not on rank arithmetic.
### Exact vs approximate search
pgvector performs exact nearest neighbor search (brute-force KNN) by default. Recall is perfect, but every query scans every vector, so latency grows linearly with your dataset. To make vector search fast at scale, you opt into an ANN index and take on its decisions yourself: HNSW or IVFFlat, index build parameters, and per-query tuning like `ef_search`.
Meilisearch made that choice for you. Vector search runs on [Hannoy](/docs/resources/internals/hannoy), a purpose-built HNSW-based, disk-backed vector store tuned for fast user-facing search at scale, with no index type to pick and no per-query knobs to tune. If your use case genuinely requires 100% recall on every query, pgvector's exact mode covers it; for search experiences, where consistent sub-50ms responses matter more than the last percentile of recall, ANN is the standard and Meilisearch delivers it without the tuning burden.
### Filtering combined with vector search
With a pgvector ANN index, `WHERE` clauses apply **after** the index scan. This is one of the most common production surprises with pgvector: a selective filter silently degrades results. Filtering down to 10% of rows with the default `ef_search` of 40 returns only about 4 results on average, and fixing it means enabling iterative scanning or tuning index parameters query by query.
Meilisearch [filtering](/docs/capabilities/filtering_sorting_faceting/overview) is native to the engine and integrated with vector search. Hannoy adapts its search strategy based on how many documents match the filter relative to the total, switching to linear scanning when the candidate set is small. Selective filters return full result sets with no hidden recall loss and nothing to tune.
### Quantization and dimension limits
pgvector offers several storage formats: `halfvec` (16-bit floats), binary vectors, and sparse vectors. Each is a schema-level decision you make per column, and changing your mind means migrating the column and rebuilding indexes.
Meilisearch offers [binary quantization](/docs/capabilities/hybrid_search/advanced/binary_quantization) as a single index setting. It is particularly effective for high-dimensional models (above roughly 1,500 to 3,000 dimensions), where the impact on recall is minimal compared to the gains in disk usage and indexing speed.
Dimension limits follow the same pattern. pgvector documents hard ceilings: 2,000 dimensions for full-precision `vector`, 4,000 for `halfvec`, and 64,000 for binary vectors. Modern high-dimensional embedding models can bump into the full-precision ceiling, forcing you into a different column type. Meilisearch documents no dimension limit, and vectors above 3,000 dimensions run in production today, with binary quantization as the standard recommendation as dimensions grow.
### Incremental index updates
pgvector's own documentation notes that `VACUUM` on an HNSW index can be slow and recommends reindexing before vacuuming. On a catalog that changes frequently, that is a recurring maintenance cost to schedule and monitor.
Hannoy was designed specifically to keep incremental updates cheap: it merges new vectors into the existing graph and re-indexes less than 1% of existing vectors on a typical insertion. Frequently updated datasets are a first-class scenario for Meilisearch, not a maintenance chore.
## When pgvector might be enough
Consider pgvector if:
* You need exact nearest neighbor search with guaranteed recall on every query
* You have already built and maintain an embedding pipeline, and vector search is an internal feature rather than a user-facing experience
* You want vectors to live next to your relational data with transactional guarantees, and search quality is not the priority
## When PostgreSQL FTS might be enough
Consider PostgreSQL full-text search if:
* You have a small dataset (thousands of documents)
* Search isn't user-facing or real-time search isn't required
* Basic keyword matching is sufficient
* You can't add additional infrastructure
* You're using a managed PostgreSQL service that restricts extensions
## Migration resources
Ready to upgrade from PostgreSQL full-text search:
* [Migrating from PostgreSQL](/docs/resources/migration/postgresql_migration) - Step-by-step data export and import guide with query and settings comparison
* [Quick start guide](/docs/getting_started/first_project) - Set up Meilisearch in minutes
* [Hybrid search getting started](/docs/capabilities/hybrid_search/getting_started) - Replace your pgvector setup with semantic and hybrid search in a few settings
* [Indexing documents](/docs/resources/internals/documents) - Import your data
PostgreSQL is a registered trademark of the PostgreSQL Global Development Group. This comparison is based on publicly available information and our own analysis.
# Meilisearch vs Qdrant
Source: https://www.meilisearch.com/docs/resources/comparisons/qdrant
Compare Meilisearch and Qdrant for semantic search. Learn the differences between hybrid search engines and vector databases.
Qdrant is an open-source vector database written in Rust, designed specifically for AI applications and semantic search. It focuses on high-performance vector operations with advanced filtering capabilities.
## Quick comparison
| | Meilisearch | Qdrant |
| ------------------------ | :---------------: | :------------------------------------: |
| **Primary focus** | Hybrid search | Vector database |
| **Full-text search** | Native, optimized | Via sparse vectors |
| **License** | MIT | Apache 2.0 |
| **Self-hosting** | Yes | Yes |
| **Embedding generation** | Built-in | Built-in (Cloud Inference) or external |
| **Typo tolerance** | Built-in | Not applicable |
| **Cloud pricing** | From \$30/month | Free 1GB tier, then usage-based |
## What Qdrant does well
### High-performance vector search
Qdrant's HNSW algorithm, optimized in Rust, delivers excellent vector search performance. Quantization can reduce memory usage significantly while maintaining accuracy.
### Filterable vector search
Qdrant's architecture integrates filtering directly into the search process rather than filtering after retrieval. This enables efficient combination of semantic similarity with metadata filters.
### Deployment flexibility
Unlike some competitors, Qdrant offers self-hosting, managed cloud, and hybrid deployment options. This flexibility supports various data sovereignty and infrastructure requirements.
### Open source
Qdrant is open-source under Apache 2.0 license, allowing inspection, modification, and self-hosting without vendor lock-in.
## When to choose Meilisearch instead
### You need strong full-text search
Meilisearch provides mature full-text search with typo tolerance, prefix matching, and sophisticated relevancy ranking. Qdrant's keyword capabilities via sparse vectors don't match the depth of a dedicated search engine.
### Typo tolerance is important
Meilisearch handles misspellings automatically with configurable tolerance. Vector search operates on embeddings of the exact query text, so typos produce different vectors and potentially different results.
### You want unified hybrid search
Meilisearch combines keyword and semantic search in a single API with adjustable balance. With Qdrant, you'd need to implement hybrid search logic yourself or use their sparse vector support.
### You prefer flexible embedding generation
Meilisearch can generate embeddings automatically through integrations with OpenAI, HuggingFace, Ollama, and any REST-compatible provider. Qdrant Cloud now offers built-in embedding via Cloud Inference, but self-hosted Qdrant still requires external embedding generation.
### Search relevancy tuning matters
Meilisearch offers configurable ranking rules, custom ranking attributes, and relevancy tuning out-of-the-box. Qdrant focuses on vector similarity with less flexibility for traditional relevancy adjustments.
### Your primary use case is site/app search
If you're building e-commerce search, documentation search, or general site search where keyword matching is essential, Meilisearch's full-text capabilities are more comprehensive.
## When to choose Qdrant
Consider Qdrant if:
* You're building AI applications where pure vector search is the primary requirement
* You need advanced vector operations like quantization and custom distance metrics
* You want to combine vector search with complex metadata filtering
* Your team manages embeddings externally and needs a dedicated vector store
* You require flexible deployment options including on-premises or hybrid cloud
* You're building recommendation systems based primarily on similarity matching
## Migration resources
If you're evaluating Meilisearch for semantic search:
* [AI-powered search guide](/docs/capabilities/hybrid_search/getting_started) - Configure hybrid search
* [Embedder setup](/docs/capabilities/hybrid_search/how_to/choose_an_embedder) - Integrate embedding providers
* [Search preview](/docs/resources/self_hosting/getting_started/search_preview) - Explore search capabilities
Qdrant is a registered trademark of Qdrant Solutions GmbH. This comparison is based on publicly available information and our own analysis.
# Meilisearch vs Typesense
Source: https://www.meilisearch.com/docs/resources/comparisons/typesense
Compare Meilisearch and Typesense, two open-source search engines focused on speed and developer experience. Learn key differences and when to choose each.
Typesense is an open-source search engine started in 2015 and first publicly released in 2018, built in C++ and focused on speed and ease of use. Like Meilisearch, it targets developer experience and typo-tolerant instant search. While both engines share similar goals, they differ significantly in architecture, language support, scalability, and licensing.
## Quick comparison
| | Meilisearch | Typesense |
| --------------------------- | :---------------------------------------------: | :------------------------------------------------: |
| **License** | MIT (CE) / BUSL-1.1 (EE) | GPL-3 (copyleft) |
| **Built with** | Rust | C++ |
| **Data storage** | Disk (memory-mapped) | RAM-only |
| **Sharding** | Yes (Enterprise Edition) | No |
| **Language support** | Optimized for CJK, Arabic, Hebrew, Thai | Unicode-based (limited CJK) |
| **Auto language detection** | Yes | No |
| **Analytics** | Full dashboard (Cloud) | Query analytics, event tracking |
| **Embedding generation** | Built-in local (Candle) + any HTTP API provider | Built-in ONNX, OpenAI, Azure OpenAI, GCP Vertex AI |
| **Conversational search** | Yes (built-in chat) | No |
| **Multi-index search** | Yes (federated search) | Yes (multi-search) |
## What Typesense does well
### Field weighting at query time
Typesense allows boosting specific fields at query time, so you can ensure matches in product titles rank higher than description matches. Meilisearch achieves similar results through [searchable attributes ordering](/docs/reference/api/settings/update-searchable-attributes), which ranks fields by priority at index time.
### Grouping results
Typesense provides the ability to group search results by a specified field, useful for deduplication or organizing results by category.
### Fast in-memory search for small datasets
By storing indexes entirely in RAM, Typesense achieves excellent search speeds for datasets that fit in memory. For small to medium datasets where memory cost is not a concern, this approach works well.
## When to choose Meilisearch instead
### Your dataset can grow beyond available RAM
This is one of the most important architectural differences. Meilisearch uses disk-based storage with memory mapping, so your dataset size is not limited by available RAM. Typesense stores indexes entirely in memory. This means a growing dataset on Typesense requires increasingly expensive hardware, and large datasets can become impractical or prohibitively costly.
### You need to scale horizontally with sharding
Meilisearch Enterprise Edition supports sharding, allowing you to distribute large indexes across multiple nodes. This is essential for production workloads that outgrow a single machine. Typesense does not support sharding. While it offers replication for high availability, every node must hold the entire dataset in RAM, which limits your ability to scale beyond what a single machine's memory can handle.
### You need robust multilingual support
Meilisearch provides optimized tokenization for Chinese, Japanese, Korean, Thai, Hebrew, Arabic, and other languages with complex word boundaries. It also automatically detects document language and applies appropriate processing. Typesense relies on Unicode-based tokenization, which struggles with languages that lack conventional word spacing or have rich morphology. If your users search in multiple languages, Meilisearch handles this out of the box.
### You want conversational search
Meilisearch offers built-in [conversational search](/docs/capabilities/conversational_search/getting_started/setup) that lets users interact with your data through natural language chat, powered by LLMs and grounded in your indexed documents. Typesense does not offer a comparable feature.
### You prefer MIT licensing
Meilisearch's Community Edition uses the permissive MIT license, giving you complete freedom to use, modify, and distribute without restrictions. Typesense uses GPL-3, a copyleft license that requires derivative works to be distributed under the same license. While GPL-3 does not restrict commercial use, the copyleft requirement can be a concern for organizations embedding search into proprietary products.
### You need comprehensive analytics
Meilisearch Cloud provides a full analytics dashboard with no-result rates, popular queries, geographic distribution, click tracking, and conversion metrics. Typesense supports query analytics and event tracking, but does not offer a comparable hosted analytics experience.
### You want maximum embedding flexibility
Meilisearch takes an open approach to embeddings. It can generate embeddings locally using [Candle](https://github.com/huggingface/candle), a Rust-based ML framework, with no external dependencies required. On top of that, Meilisearch works with any model accessible through an HTTP API, whether that's OpenAI, Mistral, Cohere, a self-hosted Ollama instance, or any other provider. This means you can run fully local AI-powered search or mix and match providers as needed. Typesense is limited to built-in ONNX models and a fixed set of cloud providers (OpenAI, Azure OpenAI, GCP Vertex AI).
### You value stability and memory safety
Meilisearch is built in Rust, a language designed to prevent entire categories of bugs at compile time, including buffer overflows, use-after-free errors, and data races. These are the types of issues that commonly cause crashes and security vulnerabilities in C++ codebases. Typesense is written in C++, where memory management is manual and these bugs are harder to catch before they reach production. In practice, this means Meilisearch benefits from stronger reliability guarantees out of the box.
## When to choose Typesense
Consider Typesense if:
* Your dataset comfortably fits in RAM and you want memory-optimized performance
* Field weighting at query time is critical for your relevancy tuning
* You need to group search results by field values
* GPL-3 licensing aligns with your project requirements
## Migration resources
If you're considering Meilisearch:
* [Meilisearch quick start](/docs/getting_started/first_project) - Get started in minutes
* [AI-powered search](/docs/capabilities/hybrid_search/getting_started) - Hybrid and semantic search capabilities
* [Conversational search](/docs/capabilities/conversational_search/getting_started/setup) - Built-in chat grounded in your data
* [Language support](/docs/resources/help/language) - Supported languages and tokenization
* [Sharding](/docs/resources/self_hosting/deployment/overview) - Scale beyond a single node
Typesense is a registered trademark of Typesense, Inc. This comparison is based on publicly available information and our own analysis.
# Migrating from Algolia to Meilisearch
Source: https://www.meilisearch.com/docs/resources/migration/algolia_migration
This guide will take you step-by-step through the creation of a script to upload data indexed by Algolia to Meilisearch.
This page aims to help current users of Algolia make the transition to Meilisearch.
For a high-level comparison of the two search companies and their products, see [our analysis of the search market](/docs/resources/comparisons/alternatives#meilisearch-vs-algolia).
## Overview
This guide will take you step-by-step through the creation of a script to upload Algolia index data to Meilisearch. Examples are provided in JavaScript, Python, and Ruby. [You can also skip directly to the finished script](#finished-script).
The migration process consists of three steps:
1. [Export your data stored in Algolia](#export-your-algolia-data)
2. [Import your data into Meilisearch](#import-your-data-into-meilisearch)
3. [Configure your Meilisearch index settings (optional)](#configure-your-index-settings)
To help with the transition, we have also included a comparison of Meilisearch and Algolia's [API methods](#api-methods) and [front-end components](#front-end-components).
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](/docs/resources/self_hosting/getting_started/quick_start).
This guide includes examples in JavaScript, Python, and Ruby. The packages used:
* **JavaScript**: [`algoliasearch`](https://www.npmjs.com/package/algoliasearch) `4.x`, [`meilisearch`](https://www.npmjs.com/package/meilisearch) (compatible with Meilisearch v1.0+)
* **Python**: [`algoliasearch`](https://pypi.org/project/algoliasearch/) `4.x`, [`meilisearch`](https://pypi.org/project/meilisearch/)
* **Ruby**: [`algolia`](https://rubygems.org/gems/algolia) `3.x`, [`meilisearch`](https://rubygems.org/gems/meilisearch)
## Export your Algolia data
### Initialize project
```bash JavaScript theme={null}
mkdir algolia-meilisearch-migration
cd algolia-meilisearch-migration
npm init -y
touch script.js
```
```bash Python theme={null}
mkdir algolia-meilisearch-migration
cd algolia-meilisearch-migration
touch script.py
```
```bash Ruby theme={null}
mkdir algolia-meilisearch-migration
cd algolia-meilisearch-migration
touch script.rb
```
### Install dependencies
```bash JavaScript theme={null}
npm install -s algoliasearch@4 meilisearch
```
```bash Python theme={null}
pip install algoliasearch meilisearch
```
```bash Ruby theme={null}
gem install algolia meilisearch
```
### Create Algolia client
You'll need your **Application ID** and **Admin API Key** to start the Algolia client. Both can be found in your [Algolia account](https://www.algolia.com/account/api-keys).
Paste the below code in your script file:
```javascript JavaScript theme={null}
const algoliaSearch = require("algoliasearch");
const algoliaClient = algoliaSearch(
"APPLICATION_ID",
"ADMIN_API_KEY"
);
const algoliaIndex = algoliaClient.initIndex("INDEX_NAME");
```
```python Python theme={null}
from algoliasearch.search.client import SearchClientSync
algolia_client = SearchClientSync("APPLICATION_ID", "ADMIN_API_KEY")
```
```ruby Ruby theme={null}
require 'algolia'
algolia_client = Algolia::SearchClient.create(
'APPLICATION_ID',
'ADMIN_API_KEY'
)
```
Replace `APPLICATION_ID` and `ADMIN_API_KEY` with your Algolia application ID and admin API key respectively.
Replace `INDEX_NAME` with the name of the Algolia index you would like to migrate to Meilisearch.
### Fetch data from Algolia
To fetch all Algolia index data at once, use Algolia's [`browseObjects`](https://www.algolia.com/doc/api-reference/api-methods/browse/) method.
```javascript JavaScript theme={null}
let records = [];
await algoliaIndex.browseObjects({
batch: (hits) => {
records = records.concat(hits);
}
});
```
```python Python theme={null}
records = []
for hit in algolia_client.browse_objects(index_name="INDEX_NAME"):
records.append(hit)
```
```ruby Ruby theme={null}
records = []
algolia_client.browse_objects('INDEX_NAME') do |hit|
records << hit
end
```
The records array will contain all documents from your Algolia index. We will use `records` again later in the upload process.
## 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](/docs/resources/self_hosting/security/basic_security).
```javascript JavaScript theme={null}
const { Meilisearch } = require("meilisearch");
const meiliClient = new Meilisearch({
host: "MEILI_HOST",
apiKey: "MEILI_API_KEY",
});
const meiliIndex = meiliClient.index("MEILI_INDEX_NAME");
```
```python Python theme={null}
import meilisearch
meili_client = meilisearch.Client("MEILI_HOST", "MEILI_API_KEY")
meili_index = meili_client.index("MEILI_INDEX_NAME")
```
```ruby Ruby theme={null}
require 'meilisearch'
meili_client = MeiliSearch::Client.new(
'MEILI_HOST',
'MEILI_API_KEY'
)
meili_index = meili_client.index('MEILI_INDEX_NAME')
```
Replace `MEILI_HOST`,`MEILI_API_KEY`, and `MEILI_INDEX_NAME` with your Meilisearch host URL, Meilisearch API key, and the index name where you would like to add documents. Meilisearch will create the index if it doesn't already exist.
### Upload data to Meilisearch
Next, use `addDocumentsInBatches` to upload all your records in batches of 100,000.
```javascript JavaScript theme={null}
const BATCH_SIZE = 100000;
await meiliIndex.addDocumentsInBatches(records, BATCH_SIZE);
```
```python Python theme={null}
BATCH_SIZE = 100000
meili_index.add_documents_in_batches(records, batch_size=BATCH_SIZE)
```
```ruby Ruby theme={null}
BATCH_SIZE = 100000
meili_index.add_documents_in_batches(records, BATCH_SIZE)
```
That's all! When you're ready to run the script, enter the below command:
```bash JavaScript theme={null}
node script.js
```
```bash Python theme={null}
python script.py
```
```bash Ruby theme={null}
ruby script.rb
```
### Finished script
```javascript JavaScript theme={null}
const algoliaSearch = require("algoliasearch");
const { Meilisearch } = require("meilisearch");
const BATCH_SIZE = 100000;
(async () => {
const algoliaClient = algoliaSearch("APPLICATION_ID", "ADMIN_API_KEY");
const algoliaIndex = algoliaClient.initIndex("INDEX_NAME");
let records = [];
await algoliaIndex.browseObjects({
batch: (hits) => {
records = records.concat(hits);
}
});
const meiliClient = new Meilisearch({
host: "MEILI_HOST",
apiKey: "MEILI_API_KEY",
});
const meiliIndex = meiliClient.index("MEILI_INDEX_NAME");
await meiliIndex.addDocumentsInBatches(records, BATCH_SIZE);
})();
```
```python Python theme={null}
from algoliasearch.search.client import SearchClientSync
import meilisearch
BATCH_SIZE = 100000
# Fetch all documents from Algolia
algolia_client = SearchClientSync("APPLICATION_ID", "ADMIN_API_KEY")
records = []
for hit in algolia_client.browse_objects(index_name="INDEX_NAME"):
records.append(hit)
print(f"Fetched {len(records)} documents from Algolia")
# Upload to Meilisearch
meili_client = meilisearch.Client("MEILI_HOST", "MEILI_API_KEY")
meili_index = meili_client.index("MEILI_INDEX_NAME")
meili_index.add_documents_in_batches(records, batch_size=BATCH_SIZE)
print("Migration complete")
```
```ruby Ruby theme={null}
require 'algolia'
require 'meilisearch'
BATCH_SIZE = 100000
# Fetch all documents from Algolia
algolia_client = Algolia::SearchClient.create(
'APPLICATION_ID',
'ADMIN_API_KEY'
)
records = []
algolia_client.browse_objects('INDEX_NAME') do |hit|
records << hit
end
puts "Fetched #{records.length} documents from Algolia"
# Upload to Meilisearch
meili_client = MeiliSearch::Client.new('MEILI_HOST', 'MEILI_API_KEY')
meili_index = meili_client.index('MEILI_INDEX_NAME')
meili_index.add_documents_in_batches(records, BATCH_SIZE)
puts 'Migration complete'
```
## Configure your index settings
Meilisearch's default settings are designed to deliver a fast and relevant search experience that works for most use-cases.
To customize your index settings, we recommend following [this guide](/docs/resources/internals/indexes#index-settings). To learn more about the differences between settings in Algolia and Meilisearch, read on.
### Index settings vs. search parameters
One of the key usage differences between Algolia and Meilisearch is how they approach index settings and search parameters.
**In Algolia,** [API parameters](https://www.algolia.com/doc/api-reference/api-parameters/) are a flexible category that includes both index settings and search parameters. Many API parameters can be used either at indexing time (to set default behavior) or at search time (to override that behavior).
**In Meilisearch,** [index settings](/docs/reference/api/settings/list-all-settings) and [search parameters](/docs/reference/api/search/search-with-post) are two distinct categories. Settings affect all searches on an index, while parameters affect the results of a single search.
Some Meilisearch parameters require index settings to be configured beforehand. For example, you must first configure the index setting `sortableAttributes` to use the search parameter `sort`. However, unlike in Algolia, an index setting can never be used as a parameter and vice versa.
### Settings and parameters comparison
The below table compares Algolia's **API parameters** with the equivalent Meilisearch **setting** or **search parameter**. The **Type** column indicates whether the Meilisearch equivalent is a search parameter (param), an index setting (setting), or both.
#### Query and pagination
| Algolia | Meilisearch | Type |
| :-------------------- | :--------------------------------------------------------------------- | :------ |
| `query` | `q` | param |
| `offset` | `offset` | param |
| `length` | `limit` | param |
| `page` | `page` | param |
| `hitsPerPage` | `hitsPerPage` | param |
| `paginatedHits` limit | [`pagination.maxTotalHits`](/docs/reference/api/settings/update-pagination) | setting |
#### Filtering and sorting
| Algolia | Meilisearch | Type |
| :----------------------- | :-------------------------------------------------------------------------------------------------------------- | :-------------- |
| `filters` | `filter` | param |
| `facets` | `facets` | param |
| `attributesForFaceting` | [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) | setting |
| `maxValuesPerFacet` | [`faceting.maxValuesPerFacet`](/docs/reference/api/settings/update-faceting) | setting |
| `sortFacetValuesBy` | [`faceting.sortFacetValuesBy`](/docs/reference/api/settings/update-faceting) | setting |
| `maxFacetHits` | [`facetSearch`](/docs/reference/api/settings/update-facetsearch) | setting |
| Sorting (using replicas) | [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes) + `sort` param (no replicas required) | setting + param |
| `distinct` | `distinct` (per-query) or [`distinctAttribute`](/docs/reference/api/settings/update-distinctattribute) (index-wide) | param + setting |
| `attributeForDistinct` | [`distinctAttribute`](/docs/reference/api/settings/update-distinctattribute) | setting |
#### Geo search
| Algolia | Meilisearch | Type |
| :------------------------------ | :----------------------------------------------------- | :---- |
| `aroundLatLng` / `aroundRadius` | `_geoRadius(lat, lng, radius)` in `filter` | param |
| `insideBoundingBox` | `_geoBoundingBox([lat, lng], [lat, lng])` in `filter` | param |
| `insidePolygon` | `_geoPolygon([lat, lng], [lat, lng], ...)` in `filter` | param |
| `aroundPrecision` | No direct equivalent | N/A |
#### Highlighting and snippets
| Algolia | Meilisearch | Type |
| :---------------------------------- | :-------------------------------- | :---- |
| `attributesToHighlight` | `attributesToHighlight` | param |
| `highlightPreTag` | `highlightPreTag` | param |
| `highlightPostTag` | `highlightPostTag` | param |
| `attributesToSnippet` | `attributesToCrop` + `cropLength` | param |
| `snippetEllipsisText` | `cropMarker` | param |
| `restrictHighlightAndSnippetArrays` | Not supported | N/A |
#### Attributes and ranking
| Algolia | Meilisearch | Type |
| :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------- | :------ |
| `searchableAttributes` | [`searchableAttributes`](/docs/reference/api/settings/update-searchableattributes) | setting |
| `restrictSearchableAttributes` | `attributesToSearchOn` | param |
| `attributesToRetrieve` | `attributesToRetrieve` | param |
| `unretrievableAttributes` | No direct equivalent; achieved by removing attributes from [`displayedAttributes`](/docs/reference/api/settings/update-displayedattributes) | setting |
| `ranking` | [`rankingRules`](/docs/reference/api/settings/update-rankingrules) | setting |
| `customRanking` | Integrated within [`rankingRules`](/docs/reference/api/settings/update-rankingrules) | setting |
| `getRankingInfo` | `showRankingScore` / `showRankingScoreDetails` | param |
#### Typo tolerance and language
| Algolia | Meilisearch | Type |
| :--------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :-------------- |
| `typoTolerance` | [`typoTolerance`](/docs/reference/api/settings/update-typotolerance) | setting |
| `disableTypoToleranceOnAttributes` | `typoTolerance.disableOnAttributes` | setting |
| `removeStopWords` | [`stopWords`](/docs/reference/api/settings/update-stopwords) | setting |
| `synonyms` | [`synonyms`](/docs/reference/api/settings/update-synonyms) | setting |
| `separatorsToIndex` | [`separatorTokens`](/docs/reference/api/settings/get-separatortokens) / [`nonSeparatorTokens`](/docs/reference/api/settings/get-nonseparatortokens) | setting |
| `naturalLanguages` | [`localizedAttributes`](/docs/reference/api/settings/update-localizedattributes) setting + `locales` param | setting + param |
| `queryType` (prefix matching) | [`prefixSearch`](/docs/reference/api/settings/update-prefixsearch) | setting |
| `removeWordsIfNoResults` | Automatically supported via `matchingStrategy` param | param |
#### AI and vector search
| Algolia | Meilisearch | Type |
| :------------------ | :------------------------------------------------------------------------------- | :-------------- |
| NeuralSearch `mode` | `hybrid` param + [`embedders`](/docs/reference/api/settings/update-embedders) setting | setting + param |
| `enableReRanking` | Integrated in `hybrid` search | param |
| AI personalization | `personalize` | param |
#### Other
| Algolia | Meilisearch | Type |
| :----------------------------- | :------------------------------------------------------------------------- | :---- |
| `analytics` / `clickAnalytics` | Separate [Analytics API](/docs/capabilities/analytics/advanced/events_endpoint) | N/A |
| `disablePrefixOnAttributes` | Not supported | N/A |
| `relevancyStrictness` | `rankingScoreThreshold` | param |
## API methods
This section compares Algolia and Meilisearch's respective API methods, using JavaScript for reference.
| Method | Algolia | Meilisearch |
| :-------------------- | :---------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------- |
| Index Instantiation | `client.initIndex()`
Here, client is an Algolia instance. | `client.index()`
Here, client is a Meilisearch instance. |
| Create Index | Algolia automatically creates an index the first time you add a record or settings. | The same applies to Meilisearch, but users can also create an index explicitly: `client.createIndex(string indexName)` |
| Get All Indexes | `client.listIndices()` | `client.getIndexes()` |
| Get Single Index | No method available | `client.getIndex(string indexName)` |
| Delete Index | `index.delete()` | `client.deleteIndex(string indexName)` |
| Get Index Settings | `index.getSettings()` | `index.getSettings()` |
| Update Index Settings | `index.setSettings(object settings)` | `index.updateSettings(object settings)` |
| Search Method | `index.search(string query, { searchParameters, requestOptions })` | `index.search(string query, object searchParameters)` |
| Add Object | `index.saveObjects(array objects)` | `index.addDocuments(array objects)` |
| Partial Update Object | `index.partialUpdateObjects(array objects)` | `index.updateDocuments(array objects)` |
| Delete All Objects | `index.deleteObjects(array objectIDs)` | `index.deleteAllDocuments()` |
| Delete One Object | `index.deleteObject(string objectID)` | `index.deleteDocument(string id)` |
| Get All Objects | `index.getObjects(array objectIDs)` | `index.getDocuments(object params)` |
| Get Single Object | `index.getObject(str objectID)` | `index.getDocument(string id)` |
| Get API Keys | `client.listApiKeys()` | `client.getKeys()` |
| Get API Key Info | `client.getApiKey(string apiKey)` | `client.getKey(string apiKey)` |
| Create API Key | `client.addApiKey(array acl)` | `client.createKey(object configuration)` |
| Update API Key | `client.updateApiKey(string apiKey, object configuration)` | `client.updateKey(string apiKey, object configuration)` |
| Delete API Key | `client.deleteApiKey(string apiKey)` | `client.deleteKey(string apiKey)` |
## Front-end components
[InstantSearch](https://github.com/algolia/instantsearch.js) is a collection of open-source tools maintained by Algolia and used to generate front-end search UI components. To use InstantSearch with Meilisearch, you must use [Instant Meilisearch](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/instant-meilisearch).
Instant Meilisearch is a plugin connecting your Meilisearch instance with InstantSearch, giving you access to many of the same front-end components as Algolia users. You can find an up-to-date list of [the components supported by Instant Meilisearch](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/instant-meilisearch#-api-resources) in the GitHub project's README.
# Migrating from Elasticsearch to Meilisearch
Source: https://www.meilisearch.com/docs/resources/migration/elasticsearch_migration
A step-by-step guide to exporting data from Elasticsearch and importing it into Meilisearch, with a comparison of settings, queries, and API methods.
This page aims to help current users of Elasticsearch make the transition to Meilisearch.
For a high-level comparison of the two search engines, see [Meilisearch vs Elasticsearch](/docs/resources/comparisons/elasticsearch).
## Overview
This guide walks you through exporting documents from an Elasticsearch index and importing them into Meilisearch using a script in JavaScript, Python, or Ruby. [You can also skip directly to the finished script](#finished-script).
The migration process consists of four steps:
1. [Export your data from Elasticsearch](#export-your-elasticsearch-data)
2. [Prepare your data for Meilisearch](#prepare-your-data)
3. [Import your data into Meilisearch](#import-your-data-into-meilisearch)
4. [Configure your Meilisearch index settings (optional)](#configure-your-index-settings)
To help with the transition, this guide also includes a comparison of [settings and parameters](#settings-and-parameters-comparison), [query types](#query-comparison), and [API methods](#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](/docs/resources/self_hosting/getting_started/quick_start).
This guide includes examples in JavaScript, Python, and Ruby. The packages used:
* **JavaScript**: [`@elastic/elasticsearch`](https://www.npmjs.com/package/@elastic/elasticsearch) `8.x`, [`meilisearch`](https://www.npmjs.com/package/meilisearch) (compatible with Meilisearch v1.0+)
* **Python**: [`elasticsearch`](https://pypi.org/project/elasticsearch/) `8.x`, [`meilisearch`](https://pypi.org/project/meilisearch/)
* **Ruby**: [`elasticsearch`](https://rubygems.org/gems/elasticsearch) `8.x`, [`meilisearch`](https://rubygems.org/gems/meilisearch)
## Export your Elasticsearch data
### Initialize project
```bash JavaScript theme={null}
mkdir elastic-meilisearch-migration
cd elastic-meilisearch-migration
npm init -y
touch script.js
```
```bash Python theme={null}
mkdir elastic-meilisearch-migration
cd elastic-meilisearch-migration
touch script.py
```
```bash Ruby theme={null}
mkdir elastic-meilisearch-migration
cd elastic-meilisearch-migration
touch script.rb
```
### Install dependencies
```bash JavaScript theme={null}
npm install -s @elastic/elasticsearch meilisearch
```
```bash Python theme={null}
pip install elasticsearch meilisearch
```
```bash Ruby theme={null}
gem install elasticsearch meilisearch
```
### Create Elasticsearch client
You need your Elasticsearch **host URL** and authentication credentials. Paste the below code in your script:
```javascript JavaScript theme={null}
const { Client } = require("@elastic/elasticsearch");
const esClient = new Client({
node: "ELASTICSEARCH_URL",
auth: {
// Use API key authentication:
apiKey: "ELASTICSEARCH_API_KEY",
// Or use basic authentication:
// username: "USERNAME",
// password: "PASSWORD",
},
});
```
```python Python theme={null}
from elasticsearch import Elasticsearch
es_client = Elasticsearch(
"ELASTICSEARCH_URL",
# Use API key authentication:
api_key="ELASTICSEARCH_API_KEY",
# Or use basic authentication:
# basic_auth=("USERNAME", "PASSWORD"),
)
```
```ruby Ruby theme={null}
require 'elasticsearch'
es_client = Elasticsearch::Client.new(
url: 'ELASTICSEARCH_URL',
# Use API key authentication:
api_key: 'ELASTICSEARCH_API_KEY'
# Or use basic authentication:
# user: 'USERNAME',
# password: 'PASSWORD'
)
```
Replace `ELASTICSEARCH_URL` with your Elasticsearch cluster URL (for example, `https://localhost:9200`) and provide your authentication credentials.
### Fetch data from Elasticsearch
Use the [Point in Time API](https://www.elastic.co/guide/en/elasticsearch/reference/current/point-in-time-api.html) with `search_after` to paginate through all documents in the index. This approach is recommended over the deprecated Scroll API.
```javascript JavaScript theme={null}
const INDEX_NAME = "YOUR_INDEX_NAME";
const BATCH_SIZE = 10000;
async function fetchAllDocuments() {
const records = [];
// Open a Point in Time
const pit = await esClient.openPointInTime({
index: INDEX_NAME,
keep_alive: "5m",
});
let searchAfter;
while (true) {
const response = await esClient.search({
body: {
size: BATCH_SIZE,
query: { match_all: {} },
pit: { id: pit.id, keep_alive: "5m" },
sort: [{ _doc: "asc" }],
...(searchAfter && { search_after: searchAfter }),
},
});
const hits = response.hits.hits;
if (hits.length === 0) break;
records.push(...hits);
searchAfter = hits[hits.length - 1].sort;
}
// Close the Point in Time
await esClient.closePointInTime({ id: pit.id });
return records;
}
```
```python Python theme={null}
INDEX_NAME = "YOUR_INDEX_NAME"
BATCH_SIZE = 10000
def fetch_all_documents():
records = []
# Open a Point in Time
pit = es_client.open_point_in_time(
index=INDEX_NAME,
keep_alive="5m"
)
search_after = None
while True:
body = {
"size": BATCH_SIZE,
"query": {"match_all": {}},
"pit": {"id": pit["id"], "keep_alive": "5m"},
"sort": [{"_doc": "asc"}],
}
if search_after:
body["search_after"] = search_after
response = es_client.search(body=body)
hits = response["hits"]["hits"]
if not hits:
break
records.extend(hits)
search_after = hits[-1]["sort"]
# Close the Point in Time
es_client.close_point_in_time(id=pit["id"])
return records
```
```ruby Ruby theme={null}
INDEX_NAME = 'YOUR_INDEX_NAME'
BATCH_SIZE = 10_000
def fetch_all_documents(es_client)
records = []
# Open a Point in Time
pit = es_client.open_point_in_time(
index: INDEX_NAME,
keep_alive: '5m'
)
search_after = nil
loop do
body = {
size: BATCH_SIZE,
query: { match_all: {} },
pit: { id: pit['id'], keep_alive: '5m' },
sort: [{ _doc: 'asc' }]
}
body[:search_after] = search_after if search_after
response = es_client.search(body: body)
hits = response['hits']['hits']
break if hits.empty?
records.concat(hits)
search_after = hits.last['sort']
end
# Close the Point in Time
es_client.close_point_in_time(body: { id: pit['id'] })
records
end
```
Replace `YOUR_INDEX_NAME` with the name of the Elasticsearch index you want to migrate.
## Prepare your data
Elasticsearch documents are wrapped in metadata (`_id`, `_index`, `_source`). You need to extract the document data from `_source` and ensure each document has a valid primary key for Meilisearch.
```javascript JavaScript theme={null}
function prepareDocuments(hits) {
return hits.map((hit) => {
const doc = hit._source;
doc.id = hit._id;
return doc;
});
}
```
```python Python theme={null}
def prepare_documents(hits):
documents = []
for hit in hits:
doc = hit["_source"]
doc["id"] = hit["_id"]
documents.append(doc)
return documents
```
```ruby Ruby theme={null}
def prepare_documents(hits)
hits.map do |hit|
doc = hit['_source']
doc['id'] = hit['_id']
doc
end
end
```
Meilisearch stores documents as flat JSON objects. If your Elasticsearch documents use nested objects or the `nested` mapping type, you must flatten them before indexing. For example, `{ "author": { "name": "John" } }` should become `{ "author_name": "John" }` or kept as-is if you only need it for display purposes. Only top-level fields can be used for filtering, sorting, and searching.
### Handle geo data
If your Elasticsearch documents use `geo_point` fields, convert them to Meilisearch's `_geo` format:
```javascript JavaScript theme={null}
function convertGeoFields(doc, geoFieldName) {
if (doc[geoFieldName]) {
const geo = doc[geoFieldName];
doc._geo = {
lat: geo.lat,
lng: geo.lon, // Elasticsearch uses "lon", Meilisearch uses "lng"
};
delete doc[geoFieldName];
}
return doc;
}
```
```python Python theme={null}
def convert_geo_fields(doc, geo_field_name):
if geo_field_name in doc:
geo = doc[geo_field_name]
doc["_geo"] = {
"lat": geo["lat"],
"lng": geo["lon"], # Elasticsearch uses "lon", Meilisearch uses "lng"
}
del doc[geo_field_name]
return doc
```
```ruby Ruby theme={null}
def convert_geo_fields(doc, geo_field_name)
if doc[geo_field_name]
geo = doc[geo_field_name]
doc['_geo'] = {
'lat' => geo['lat'],
'lng' => geo['lon'] # Elasticsearch uses "lon", Meilisearch uses "lng"
}
doc.delete(geo_field_name)
end
doc
end
```
## 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](/docs/resources/self_hosting/security/basic_security).
```javascript JavaScript theme={null}
const { Meilisearch } = require("meilisearch");
const meiliClient = new Meilisearch({
host: "MEILI_HOST",
apiKey: "MEILI_API_KEY",
});
const meiliIndex = meiliClient.index("MEILI_INDEX_NAME");
```
```python Python theme={null}
import meilisearch
meili_client = meilisearch.Client("MEILI_HOST", "MEILI_API_KEY")
meili_index = meili_client.index("MEILI_INDEX_NAME")
```
```ruby Ruby theme={null}
require 'meilisearch'
meili_client = MeiliSearch::Client.new('MEILI_HOST', 'MEILI_API_KEY')
meili_index = meili_client.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.
```javascript JavaScript theme={null}
const UPLOAD_BATCH_SIZE = 100000;
await meiliIndex.addDocumentsInBatches(documents, UPLOAD_BATCH_SIZE);
```
```python Python theme={null}
UPLOAD_BATCH_SIZE = 100000
meili_index.add_documents_in_batches(documents, batch_size=UPLOAD_BATCH_SIZE)
```
```ruby Ruby theme={null}
UPLOAD_BATCH_SIZE = 100000
meili_index.add_documents_in_batches(documents, UPLOAD_BATCH_SIZE)
```
When you're ready, run the script:
```bash JavaScript theme={null}
node script.js
```
```bash Python theme={null}
python script.py
```
```bash Ruby theme={null}
ruby script.rb
```
### Finished script
```javascript JavaScript theme={null}
const { Client } = require("@elastic/elasticsearch");
const { Meilisearch } = require("meilisearch");
const ES_INDEX = "YOUR_INDEX_NAME";
const FETCH_BATCH_SIZE = 10000;
const UPLOAD_BATCH_SIZE = 100000;
(async () => {
// Connect to Elasticsearch
const esClient = new Client({
node: "ELASTICSEARCH_URL",
auth: {
apiKey: "ELASTICSEARCH_API_KEY",
},
});
// Fetch all documents using Point in Time
const records = [];
const pit = await esClient.openPointInTime({
index: ES_INDEX,
keep_alive: "5m",
});
let searchAfter;
while (true) {
const response = await esClient.search({
body: {
size: FETCH_BATCH_SIZE,
query: { match_all: {} },
pit: { id: pit.id, keep_alive: "5m" },
sort: [{ _doc: "asc" }],
...(searchAfter && { search_after: searchAfter }),
},
});
const hits = response.hits.hits;
if (hits.length === 0) break;
records.push(...hits);
searchAfter = hits[hits.length - 1].sort;
}
await esClient.closePointInTime({ id: pit.id });
// Prepare documents for Meilisearch
const documents = records.map((hit) => {
const doc = hit._source;
doc.id = hit._id;
return doc;
});
console.log(`Fetched ${documents.length} documents from Elasticsearch`);
// 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");
})();
```
```python Python theme={null}
from elasticsearch import Elasticsearch
import meilisearch
ES_INDEX = "YOUR_INDEX_NAME"
FETCH_BATCH_SIZE = 10000
UPLOAD_BATCH_SIZE = 100000
# Connect to Elasticsearch
es_client = Elasticsearch(
"ELASTICSEARCH_URL",
api_key="ELASTICSEARCH_API_KEY",
)
# Fetch all documents using Point in Time
records = []
pit = es_client.open_point_in_time(index=ES_INDEX, keep_alive="5m")
search_after = None
while True:
body = {
"size": FETCH_BATCH_SIZE,
"query": {"match_all": {}},
"pit": {"id": pit["id"], "keep_alive": "5m"},
"sort": [{"_doc": "asc"}],
}
if search_after:
body["search_after"] = search_after
response = es_client.search(body=body)
hits = response["hits"]["hits"]
if not hits:
break
records.extend(hits)
search_after = hits[-1]["sort"]
es_client.close_point_in_time(id=pit["id"])
# Prepare documents for Meilisearch
documents = []
for hit in records:
doc = hit["_source"]
doc["id"] = hit["_id"]
documents.append(doc)
print(f"Fetched {len(documents)} documents from Elasticsearch")
# Upload to Meilisearch
meili_client = meilisearch.Client("MEILI_HOST", "MEILI_API_KEY")
meili_index = meili_client.index("MEILI_INDEX_NAME")
meili_index.add_documents_in_batches(documents, batch_size=UPLOAD_BATCH_SIZE)
print("Migration complete")
```
```ruby Ruby theme={null}
require 'elasticsearch'
require 'meilisearch'
ES_INDEX = 'YOUR_INDEX_NAME'
FETCH_BATCH_SIZE = 10_000
UPLOAD_BATCH_SIZE = 100_000
# Connect to Elasticsearch
es_client = Elasticsearch::Client.new(
url: 'ELASTICSEARCH_URL',
api_key: 'ELASTICSEARCH_API_KEY'
)
# Fetch all documents using Point in Time
records = []
pit = es_client.open_point_in_time(index: ES_INDEX, keep_alive: '5m')
search_after = nil
loop do
body = {
size: FETCH_BATCH_SIZE,
query: { match_all: {} },
pit: { id: pit['id'], keep_alive: '5m' },
sort: [{ _doc: 'asc' }]
}
body[:search_after] = search_after if search_after
response = es_client.search(body: body)
hits = response['hits']['hits']
break if hits.empty?
records.concat(hits)
search_after = hits.last['sort']
end
es_client.close_point_in_time(body: { id: pit['id'] })
# Prepare documents for Meilisearch
documents = records.map do |hit|
doc = hit['_source']
doc['id'] = hit['_id']
doc
end
puts "Fetched #{documents.length} documents from Elasticsearch"
# Upload to Meilisearch
meili_client = MeiliSearch::Client.new('MEILI_HOST', 'MEILI_API_KEY')
meili_index = meili_client.index('MEILI_INDEX_NAME')
meili_index.add_documents_in_batches(documents, UPLOAD_BATCH_SIZE)
puts 'Migration complete'
```
## Configure your index settings
Meilisearch's default settings deliver relevant, typo-tolerant search out of the box. However, if your Elasticsearch index relies on specific mappings or analyzers, you may want to configure equivalent Meilisearch settings.
To customize your index settings, see [configuring index settings](/docs/resources/internals/indexes#index-settings). To understand the differences between Elasticsearch and Meilisearch settings, read on.
### Key conceptual differences
**Elasticsearch** uses explicit [mappings](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html) to define how each field is indexed, analyzed, and stored. You must configure analyzers, tokenizers, and field types before indexing data. Search behavior is controlled through a complex [Query DSL](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html).
**Meilisearch** takes a different approach: all fields are automatically indexed and searchable by default. You refine behavior through [index settings](/docs/reference/api/settings/list-all-settings) (which affect all searches) and [search parameters](/docs/reference/api/search/search-with-post) (which affect a single query). Features like typo tolerance, prefix search, and ranking work out of the box without configuration.
This means many Elasticsearch configurations have no direct equivalent in Meilisearch because the behavior is automatic. For example, you don't need to configure analyzers for typo tolerance, prefix matching, or stop words: Meilisearch handles these by default.
### Settings and parameters comparison
The below tables compare Elasticsearch **mappings**, **settings**, and **query parameters** with the equivalent Meilisearch features.
#### Index mappings and field configuration
| Elasticsearch | Meilisearch | Notes |
| :---------------------------------- | :---------------------------------------------------------------------------- | :---------------------------------------------------------------------------- |
| `mappings.properties` (field types) | Automatic | Meilisearch infers field types automatically |
| `properties.*.type: "text"` | [`searchableAttributes`](/docs/reference/api/settings/update-searchableattributes) | All fields are searchable by default; use this setting to restrict or reorder |
| `properties.*.type: "keyword"` | [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) | Add fields you want to filter or facet on |
| `properties.*.index: false` | [`displayedAttributes`](/docs/reference/api/settings/update-displayedattributes) | Control which fields appear in results |
| `properties.*.type: "geo_point"` | `_geo` field with `lat`/`lng` | Add `_geo` to `filterableAttributes` and `sortableAttributes` |
| `properties.*.type: "nested"` | Flatten to top-level fields | Meilisearch does not support nested object queries |
| `_source.excludes` | [`displayedAttributes`](/docs/reference/api/settings/update-displayedattributes) | Only list the fields you want returned |
| `enabled: false` | Omit from `searchableAttributes` | Fields are still stored but not searched |
#### Analysis and text processing
| Elasticsearch | Meilisearch | Notes |
| :----------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------- |
| `analysis.analyzer` | Automatic | Meilisearch uses a built-in language-aware analyzer |
| `analysis.tokenizer` | [`separatorTokens`](/docs/reference/api/settings/get-separatortokens) / [`nonSeparatorTokens`](/docs/reference/api/settings/get-nonseparatortokens) | Customize word boundary behavior |
| `analysis.filter.stop` | [`stopWords`](/docs/reference/api/settings/update-stopwords) | Define words to ignore during search |
| `analysis.filter.synonym` | [`synonyms`](/docs/reference/api/settings/update-synonyms) | Define equivalent terms |
| `analysis.filter.stemmer` | Automatic | Built-in stemming via [language detection](/docs/reference/api/settings/update-localizedattributes) |
| `settings.index.analysis.normalizer` | Automatic | Meilisearch normalizes Unicode, casing, and diacritics automatically |
| Language-specific analyzers | [`localizedAttributes`](/docs/reference/api/settings/update-localizedattributes) | Assign languages to specific fields |
#### Search query parameters
| Elasticsearch | Meilisearch | Notes |
| :---------------------------------------- | :--------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------- |
| `query.match` / `query.multi_match` | `q` search param | Meilisearch searches all `searchableAttributes` by default |
| `query.term` / `query.terms` | `filter` search param | Use filter expressions for exact matches |
| `query.bool.filter` | `filter` search param | Supports `AND`, `OR`, `NOT`, `()` operators |
| `query.bool.must` / `should` / `must_not` | `filter` + `q` | Combine search query with filter expressions |
| `query.range` | `filter` search param | Use operators like `field > value` or `field value1 TO value2` |
| `query.fuzzy` / `fuzziness` | Automatic | Built-in [typo tolerance](/docs/reference/api/settings/update-typotolerance), configurable per index |
| `query.prefix` | Automatic | Built-in [prefix search](/docs/reference/api/settings/update-prefixsearch) on the last query word |
| `query.knn` | `hybrid` + `vector` search params | Requires [`embedders`](/docs/reference/api/settings/update-embedders) setting |
| `query.geo_distance` | `_geoRadius(lat, lng, radius)` in `filter` | Requires `_geo` in `filterableAttributes` |
| `query.geo_bounding_box` | `_geoBoundingBox([lat, lng], [lat, lng])` in `filter` | Requires `_geo` in `filterableAttributes` |
| `highlight` | `attributesToHighlight` + `highlightPreTag` + `highlightPostTag` | Search params |
| `_source` | `attributesToRetrieve` | Search param |
| `from` / `size` | `offset` / `limit` or `page` / `hitsPerPage` | Search params |
| `sort` | `sort` search param | Requires [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes) setting |
| `search_after` | `offset` / `limit` or `page` / `hitsPerPage` | Meilisearch uses simpler pagination |
| `aggs` (aggregations) | `facets` search param | Returns value counts; complex aggregations are not supported |
| `explain` | `showRankingScore` / `showRankingScoreDetails` | Search params |
| `collapse` | `distinct` search param or [`distinctAttribute`](/docs/reference/api/settings/update-distinctattribute) setting | Field-level deduplication |
| `min_score` | `rankingScoreThreshold` | Search param |
#### Index settings
| Elasticsearch | Meilisearch | Notes |
| :------------------------- | :--------------------------------------------------------------------- | :---------------------------------------------------------------------------------- |
| `index.number_of_replicas` | Automatic (Meilisearch Cloud) | [Meilisearch Cloud](https://www.meilisearch.com/cloud) handles replication |
| `index.number_of_shards` | Automatic (Meilisearch Cloud) | [Meilisearch Cloud](https://www.meilisearch.com/cloud) handles sharding |
| `index.max_result_window` | [`pagination.maxTotalHits`](/docs/reference/api/settings/update-pagination) | Default is 1000 in Meilisearch |
| `index.refresh_interval` | Automatic | Meilisearch indexes asynchronously via [tasks](/docs/reference/api/tasks/list-all-tasks) |
### What you can simplify
Many Elasticsearch configurations become unnecessary when migrating to Meilisearch:
* **Analyzers and tokenizers**: Meilisearch's built-in text processing handles tokenization, normalization, stemming, and language detection automatically.
* **Mapping definitions**: Field types are inferred. You don't need to define mappings before indexing documents.
* **Replicas and shards**: Meilisearch Cloud manages these automatically. Self-hosted instances run as a single process.
* **Index lifecycle management**: Meilisearch doesn't require index rotation, rollover policies, or shard management.
* **Query complexity**: Most Elasticsearch `bool` queries with nested `must`, `should`, and `filter` clauses translate to a simple `q` parameter combined with a `filter` string.
## Query comparison
This section shows how common Elasticsearch queries translate to Meilisearch.
### Full-text search
**Elasticsearch:**
```json theme={null}
{
"query": {
"match": {
"title": "search engine"
}
}
}
```
**Meilisearch:**
```json theme={null}
{
"q": "search engine"
}
```
Meilisearch searches all `searchableAttributes` by default. To restrict to a specific field, use the `attributesToSearchOn` search parameter.
### Filtering
**Elasticsearch:**
```json theme={null}
{
"query": {
"bool": {
"must": { "match": { "title": "search" } },
"filter": [
{ "term": { "status": "published" } },
{ "range": { "price": { "gte": 10, "lte": 50 } } }
]
}
}
}
```
**Meilisearch:**
```json theme={null}
{
"q": "search",
"filter": "status = published AND price >= 10 AND price <= 50"
}
```
Attributes used in `filter` must first be added to [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes).
### Sorting
**Elasticsearch:**
```json theme={null}
{
"query": { "match_all": {} },
"sort": [
{ "price": "asc" },
{ "date": "desc" }
]
}
```
**Meilisearch:**
```json theme={null}
{
"q": "",
"sort": ["price:asc", "date:desc"]
}
```
Attributes used in `sort` must first be added to [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes).
### Faceted search
**Elasticsearch:**
```json theme={null}
{
"query": { "match": { "title": "shoes" } },
"aggs": {
"colors": { "terms": { "field": "color" } },
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 50 },
{ "from": 50, "to": 100 },
{ "from": 100 }
]
}
}
}
}
```
**Meilisearch:**
```json theme={null}
{
"q": "shoes",
"facets": ["color", "price"]
}
```
Meilisearch returns value distributions for each facet. Range aggregations are not supported: use `filter` to narrow results by range.
### Geo search
**Elasticsearch:**
```json theme={null}
{
"query": {
"geo_distance": {
"distance": "10km",
"location": { "lat": 48.8566, "lon": 2.3522 }
}
},
"sort": [
{ "_geo_distance": { "location": { "lat": 48.8566, "lon": 2.3522 }, "order": "asc" } }
]
}
```
**Meilisearch:**
```json theme={null}
{
"filter": "_geoRadius(48.8566, 2.3522, 10000)",
"sort": ["_geoPoint(48.8566, 2.3522):asc"]
}
```
The `_geo` attribute must be added to both [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) and [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes).
## API methods
This section compares Elasticsearch and Meilisearch API operations.
| Operation | Elasticsearch | Meilisearch |
| :----------------- | :-------------------------------- | :----------------------------------------------------- |
| Create index | `PUT /my-index` | `POST /indexes` |
| Delete index | `DELETE /my-index` | `DELETE /indexes/{index_uid}` |
| Get index info | `GET /my-index` | `GET /indexes/{index_uid}` |
| List indexes | `GET /_cat/indices` | `GET /indexes` |
| Index document | `POST /my-index/_doc` | `POST /indexes/{index_uid}/documents` |
| Bulk index | `POST /_bulk` | `POST /indexes/{index_uid}/documents` (accepts arrays) |
| Get document | `GET /my-index/_doc/{id}` | `GET /indexes/{index_uid}/documents/{id}` |
| Delete document | `DELETE /my-index/_doc/{id}` | `DELETE /indexes/{index_uid}/documents/{id}` |
| Delete by query | `POST /my-index/_delete_by_query` | `POST /indexes/{index_uid}/documents/delete` |
| Search | `POST /my-index/_search` | `POST /indexes/{index_uid}/search` |
| Multi-search | `POST /_msearch` | `POST /multi-search` |
| Get settings | `GET /my-index/_settings` | `GET /indexes/{index_uid}/settings` |
| Update settings | `PUT /my-index/_settings` | `PATCH /indexes/{index_uid}/settings` |
| Create API key | `POST /_security/api_key` | `POST /keys` |
| Get cluster health | `GET /_cluster/health` | `GET /health` |
| Get task status | `GET /_tasks/{task_id}` | `GET /tasks/{task_uid}` |
## Front-end components
Elasticsearch offers [Search UI](https://github.com/elastic/search-ui), a React component library for building search interfaces. Meilisearch is compatible with Algolia's [InstantSearch](https://github.com/algolia/instantsearch.js) libraries through [Instant Meilisearch](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/instant-meilisearch).
InstantSearch provides a rich set of pre-built widgets for search boxes, hits, facets, pagination, and more. You can find an up-to-date list of [the components supported by Instant Meilisearch](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/instant-meilisearch#-api-resources) in the GitHub project's README.
# Migrating to Meilisearch Cloud
Source: https://www.meilisearch.com/docs/resources/migration/migrating_cloud
Meilisearch Cloud is the recommended way of using Meilisearch. This guide walks you through migrating Meilisearch from a self-hosted installation to Meilisearch Cloud.
## Requirements
* A running Meilisearch instance
* A command-line terminal
* A Meilisearch Cloud account and project
***
## Export API
The export API pushes your data directly from your self-hosted instance to a remote Meilisearch instance without creating any intermediate files.
### 1. Create a Meilisearch Cloud project
Navigate to [Meilisearch Cloud](https://cloud.meilisearch.com) and create a new project. Once it is ready, note down your project URL and API key from the project overview.
### 2. Run the export
On your self-hosted instance, send a `POST /export` request pointing to your Cloud project:
```bash cURL theme={null}
curl \
-X POST 'MEILISEARCH_URL/export' \
-H 'Content-Type: application/json' \
--data-binary '{
"url": "TARGET_INSTANCE_URL",
"indexes": {
"*": {
"overrideSettings": true
}
}
}'
```
```python Python theme={null}
client.export(
url='https://remote-meilisearch-instance.com',
api_key='masterKey',
payload_size='50 MiB',
indexes={
'movies*': {},
'books*': {},
},
)
```
```java Java theme={null}
Map indexes = new HashMap<>();
indexes.put("*", ExportIndexFilter.builder().overrideSettings(true).build());
ExportRequest request = ExportRequest.builder().url("TARGET_INSTANCE_URL").indexes(indexes).build();
client.export(request);
```
```dart Dart theme={null}
await client.export(
ExportQuery(
url: exportSinkUrl,
apiKey: 'new_instance_api_key',
payloadSize: "100 MiB",
),
);
```
Replace `TARGET_INSTANCE_URL` with your Cloud project URL and add your Cloud API key via the `apiKey` field or an `Authorization` header.
Meilisearch returns a task object. [Use the `taskUid` to monitor its progress.](/docs/capabilities/indexing/tasks_and_batches/async_operations)
### 3. Verify the migration
Once the task status is `succeeded`, open your Cloud project and run a few searches to confirm all data transferred correctly.
Meilisearch Cloud automatically generates a new master key during project creation. If you are using [security keys](/docs/resources/self_hosting/security/basic_security), update your application to use the newly generated Meilisearch Cloud API keys.
***
Congratulations, you have migrated to Meilisearch Cloud. If you encounter any problems, reach out to our support team on [Discord](https://discord.meilisearch.com).
# Migrating from MongoDB Atlas Search to Meilisearch
Source: https://www.meilisearch.com/docs/resources/migration/mongodb_migration
A step-by-step guide to exporting data from MongoDB Atlas Search and importing it into Meilisearch, with a comparison of settings, queries, and API methods.
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](/docs/resources/comparisons/mongodb).
## 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](#finished-script).
The migration process consists of four steps:
1. [Export your data from MongoDB](#export-your-mongodb-data)
2. [Prepare your data for Meilisearch](#prepare-your-data)
3. [Import your data into Meilisearch](#import-your-data-into-meilisearch)
4. [Configure your Meilisearch index settings (optional)](#configure-your-index-settings)
To help with the transition, this guide also includes a comparison of [settings and parameters](#settings-and-parameters-comparison), [query types](#query-comparison), and [API methods](#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](/docs/resources/self_hosting/getting_started/quick_start).
This guide includes examples in JavaScript, Python, and Ruby. The packages used:
* **JavaScript**: [`mongodb`](https://www.npmjs.com/package/mongodb) `6.x`, [`meilisearch`](https://www.npmjs.com/package/meilisearch) (compatible with Meilisearch v1.0+)
* **Python**: [`pymongo`](https://pypi.org/project/pymongo/) `4.x`, [`meilisearch`](https://pypi.org/project/meilisearch/)
* **Ruby**: [`mongo`](https://rubygems.org/gems/mongo) `2.x`, [`meilisearch`](https://rubygems.org/gems/meilisearch)
## Export your MongoDB data
### Initialize project
```bash JavaScript theme={null}
mkdir mongodb-meilisearch-migration
cd mongodb-meilisearch-migration
npm init -y
touch script.js
```
```bash Python theme={null}
mkdir mongodb-meilisearch-migration
cd mongodb-meilisearch-migration
touch script.py
```
```bash Ruby theme={null}
mkdir mongodb-meilisearch-migration
cd mongodb-meilisearch-migration
touch script.rb
```
### Install dependencies
```bash JavaScript theme={null}
npm install -s mongodb meilisearch
```
```bash Python theme={null}
pip install pymongo meilisearch
```
```bash Ruby theme={null}
gem install mongo meilisearch
```
### Create MongoDB client
You need your MongoDB **connection string** (URI). For Atlas, this is available in your cluster's connection settings.
```javascript JavaScript theme={null}
const { MongoClient } = require("mongodb");
const mongoClient = new MongoClient("MONGODB_URI");
```
```python Python theme={null}
from pymongo import MongoClient
mongo_client = MongoClient("MONGODB_URI")
```
```ruby Ruby theme={null}
require 'mongo'
mongo_client = Mongo::Client.new('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.
```javascript JavaScript theme={null}
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;
}
```
```python Python theme={null}
DB_NAME = "YOUR_DATABASE_NAME"
COLLECTION_NAME = "YOUR_COLLECTION_NAME"
BATCH_SIZE = 10000
def fetch_all_documents():
db = mongo_client[DB_NAME]
collection = db[COLLECTION_NAME]
records = []
cursor = collection.find({}).batch_size(BATCH_SIZE)
for doc in cursor:
records.append(doc)
return records
```
```ruby Ruby theme={null}
DB_NAME = 'YOUR_DATABASE_NAME'
COLLECTION_NAME = 'YOUR_COLLECTION_NAME'
BATCH_SIZE = 10_000
def fetch_all_documents(mongo_client)
records = []
collection = mongo_client.use(DB_NAME)[COLLECTION_NAME]
collection.find({}).batch_size(BATCH_SIZE).each do |doc|
records << doc
end
records
end
```
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.
```javascript JavaScript theme={null}
function prepareDocuments(docs) {
return docs.map((doc) => {
doc.id = doc._id.toString();
delete doc._id;
return doc;
});
}
```
```python Python theme={null}
def prepare_documents(docs):
documents = []
for doc in docs:
doc["id"] = str(doc["_id"])
del doc["_id"]
documents.append(doc)
return documents
```
```ruby Ruby theme={null}
def prepare_documents(docs)
docs.map do |doc|
doc['id'] = doc['_id'].to_s
doc.delete('_id')
doc
end
end
```
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](https://www.mongodb.com/docs/manual/reference/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.
```javascript JavaScript theme={null}
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;
}
```
```python Python theme={null}
def convert_geo_fields(doc, geo_field_name):
if geo_field_name in doc and doc[geo_field_name].get("type") == "Point":
coordinates = doc[geo_field_name]["coordinates"]
doc["_geo"] = {
"lat": coordinates[1], # GeoJSON: [lon, lat]
"lng": coordinates[0],
}
del doc[geo_field_name]
return doc
```
```ruby Ruby theme={null}
def convert_geo_fields(doc, geo_field_name)
if doc[geo_field_name] && doc[geo_field_name]['type'] == 'Point'
coordinates = doc[geo_field_name]['coordinates']
doc['_geo'] = {
'lat' => coordinates[1], # GeoJSON: [lon, lat]
'lng' => coordinates[0]
}
doc.delete(geo_field_name)
end
doc
end
```
## 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](/docs/resources/self_hosting/security/basic_security).
```javascript JavaScript theme={null}
const { Meilisearch } = require("meilisearch");
const meiliClient = new Meilisearch({
host: "MEILI_HOST",
apiKey: "MEILI_API_KEY",
});
const meiliIndex = meiliClient.index("MEILI_INDEX_NAME");
```
```python Python theme={null}
import meilisearch
meili_client = meilisearch.Client("MEILI_HOST", "MEILI_API_KEY")
meili_index = meili_client.index("MEILI_INDEX_NAME")
```
```ruby Ruby theme={null}
require 'meilisearch'
meili_client = MeiliSearch::Client.new('MEILI_HOST', 'MEILI_API_KEY')
meili_index = meili_client.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.
```javascript JavaScript theme={null}
const UPLOAD_BATCH_SIZE = 100000;
await meiliIndex.addDocumentsInBatches(documents, UPLOAD_BATCH_SIZE);
```
```python Python theme={null}
UPLOAD_BATCH_SIZE = 100000
meili_index.add_documents_in_batches(documents, batch_size=UPLOAD_BATCH_SIZE)
```
```ruby Ruby theme={null}
UPLOAD_BATCH_SIZE = 100_000
meili_index.add_documents_in_batches(documents, UPLOAD_BATCH_SIZE)
```
When you're ready, run the script:
```bash JavaScript theme={null}
node script.js
```
```bash Python theme={null}
python script.py
```
```bash Ruby theme={null}
ruby script.rb
```
### Finished script
```javascript JavaScript theme={null}
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");
})();
```
```python Python theme={null}
from pymongo import MongoClient
import meilisearch
DB_NAME = "YOUR_DATABASE_NAME"
COLLECTION_NAME = "YOUR_COLLECTION_NAME"
FETCH_BATCH_SIZE = 10000
UPLOAD_BATCH_SIZE = 100000
# Connect to MongoDB
mongo_client = MongoClient("MONGODB_URI")
db = mongo_client[DB_NAME]
collection = db[COLLECTION_NAME]
# Fetch all documents
records = []
cursor = collection.find({}).batch_size(FETCH_BATCH_SIZE)
for doc in cursor:
records.append(doc)
mongo_client.close()
# Prepare documents for Meilisearch
documents = []
for doc in records:
doc["id"] = str(doc["_id"])
del doc["_id"]
documents.append(doc)
print(f"Fetched {len(documents)} documents from MongoDB")
# Upload to Meilisearch
meili_client = meilisearch.Client("MEILI_HOST", "MEILI_API_KEY")
meili_index = meili_client.index("MEILI_INDEX_NAME")
meili_index.add_documents_in_batches(documents, batch_size=UPLOAD_BATCH_SIZE)
print("Migration complete")
```
```ruby Ruby theme={null}
require 'mongo'
require 'meilisearch'
DB_NAME = 'YOUR_DATABASE_NAME'
COLLECTION_NAME = 'YOUR_COLLECTION_NAME'
FETCH_BATCH_SIZE = 10_000
UPLOAD_BATCH_SIZE = 100_000
# Connect to MongoDB
mongo_client = Mongo::Client.new('MONGODB_URI')
collection = mongo_client.use(DB_NAME)[COLLECTION_NAME]
# Fetch all documents
records = []
collection.find({}).batch_size(FETCH_BATCH_SIZE).each do |doc|
records << doc
end
mongo_client.close
# Prepare documents for Meilisearch
documents = records.map do |doc|
doc['id'] = doc['_id'].to_s
doc.delete('_id')
doc
end
puts "Fetched #{documents.length} documents from MongoDB"
# Upload to Meilisearch
meili_client = MeiliSearch::Client.new('MEILI_HOST', 'MEILI_API_KEY')
meili_index = meili_client.index('MEILI_INDEX_NAME')
meili_index.add_documents_in_batches(documents, UPLOAD_BATCH_SIZE)
puts '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](https://www.mongodb.com/docs/atlas/atlas-search/create-index/) 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](/docs/reference/api/settings/list-all-settings) (which affect all searches) and [search parameters](/docs/reference/api/search/search-with-post) (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](/docs/capabilities/hybrid_search/getting_started) source such as OpenAI, HuggingFace, or a custom REST endpoint:
```bash theme={null}
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](/docs/capabilities/hybrid_search/getting_started).
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:
```bash theme={null}
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`:
```javascript theme={null}
// 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`](/docs/reference/api/settings/update-filterableattributes) and [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes):
```bash theme={null}
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 Search | Meilisearch | Notes |
| :---------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------- |
| Dynamic field mappings | Automatic | Meilisearch indexes all fields by default |
| Static field mappings | [`searchableAttributes`](/docs/reference/api/settings/update-searchableattributes) / [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) | Use these to restrict or reorder searchable fields and enable filtering |
| Lucene analyzer (`lucene.standard`, etc.) | Automatic | Meilisearch uses a built-in language-aware analyzer |
| Custom analyzers | [`separatorTokens`](/docs/reference/api/settings/get-separatortokens) / [`nonSeparatorTokens`](/docs/reference/api/settings/get-nonseparatortokens) | Customize word boundary behavior |
| `storedSource` | [`displayedAttributes`](/docs/reference/api/settings/update-displayedattributes) | Control which fields appear in results |
| `synonyms` (in index definition) | [`synonyms`](/docs/reference/api/settings/update-synonyms) | Define equivalent terms |
| `type: "string"` mapping | Automatic | Field types are inferred |
| `type: "number"` mapping | Automatic | Field types are inferred |
| `type: "geo"` mapping | `_geo` field with `lat`/`lng` | Add `_geo` to `filterableAttributes` and `sortableAttributes` |
### Search operators
| MongoDB Atlas Search | Meilisearch | Notes |
| :------------------------------------------------------------- | :-------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------- |
| `$search` with `text` operator | `q` search param | Meilisearch searches all `searchableAttributes` by default |
| `$search` with `phrase` operator | `q` search param | Use `"quoted phrase"` in the query string for phrase matching |
| `$search` with `wildcard` operator | Automatic prefix search | Meilisearch applies prefix search on the last query word by default |
| `$search` with `regex` operator | No direct equivalent | Use `filter` for exact matching on specific field values |
| `$search` with `compound` (`must`/`should`/`mustNot`/`filter`) | `q` + `filter` | Combine search query with filter expressions using `AND`, `OR`, `NOT` |
| `$search` with `range` operator | `filter` search param | Use operators like `field > value` or `field value1 TO value2` |
| `$search` with `near` (geo) | `_geoRadius(lat, lng, radius)` or `_geoBoundingBox([lat, lng], [lat, lng])` in `filter` | Requires `_geo` in `filterableAttributes` |
| `$vectorSearch` | `hybrid` + auto-embedder | No need to precompute vectors, Meilisearch embeds queries automatically |
| `$searchMeta` with `facet` collector | `facets` search param | Returns value distributions for each facet |
| `highlight` option in `$search` | `attributesToHighlight` + `highlightPreTag` + `highlightPostTag` | Search params |
| `$sort` stage after `$search` | `sort` search param | Requires [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes) |
| `$skip` / `$limit` stages | `offset` / `limit` or `page` / `hitsPerPage` | Search params |
| `scoreDetails: true` | `showRankingScoreDetails` | Search param |
| `count` in `$searchMeta` | Automatic | Meilisearch returns `estimatedTotalHits` (or `totalHits` with exhaustive pagination) |
### Index settings
| MongoDB Atlas Search | Meilisearch | Notes |
| :----------------------- | :---------------------------------------------------------------------------- | :----------------------------------------------------- |
| Search index definition | Automatic | No index definition needed before searching |
| Analyzer configuration | Automatic | Built-in language-aware text processing |
| `storedSource` | [`displayedAttributes`](/docs/reference/api/settings/update-displayedattributes) | Control which fields appear in results |
| `synonyms` mapping | [`synonyms`](/docs/reference/api/settings/update-synonyms) | Define equivalent terms |
| Index on specific fields | [`searchableAttributes`](/docs/reference/api/settings/update-searchableattributes) | All fields searchable by default; use this to restrict |
| Atlas cluster scaling | Automatic ([Meilisearch Cloud](https://www.meilisearch.com/cloud)) | Meilisearch Cloud handles scaling |
## Query comparison
This section shows how common MongoDB Atlas Search aggregation pipelines translate to Meilisearch search requests.
### Full-text search
**MongoDB Atlas Search:**
```json theme={null}
db.collection.aggregate([
{
"$search": {
"text": {
"query": "search engine",
"path": "title"
}
}
}
])
```
**Meilisearch:**
```json theme={null}
{
"q": "search engine"
}
```
Meilisearch searches all `searchableAttributes` by default. To restrict to a specific field, use the `attributesToSearchOn` search parameter.
### Filtered search
**MongoDB Atlas Search:**
```json theme={null}
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:**
```json theme={null}
{
"q": "laptop",
"filter": "price >= 500 AND price <= 2000 AND category = electronics"
}
```
Attributes used in `filter` must first be added to [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes).
### Sorting
**MongoDB Atlas Search:**
```json theme={null}
db.collection.aggregate([
{
"$search": {
"text": {
"query": "shoes",
"path": "title"
}
}
},
{ "$sort": { "price": 1, "date": -1 } }
])
```
**Meilisearch:**
```json theme={null}
{
"q": "shoes",
"sort": ["price:asc", "date:desc"]
}
```
Attributes used in `sort` must first be added to [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes).
### Faceted search
**MongoDB Atlas Search:**
```json theme={null}
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:**
```json theme={null}
{
"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.
### Geo search
**MongoDB Atlas Search:**
```json theme={null}
db.collection.aggregate([
{
"$search": {
"near": {
"path": "location",
"origin": {
"type": "Point",
"coordinates": [2.3522, 48.8566]
},
"pivot": 10000
}
}
}
])
```
**Meilisearch:**
```json theme={null}
{
"filter": "_geoRadius(48.8566, 2.3522, 10000)",
"sort": ["_geoPoint(48.8566, 2.3522):asc"]
}
```
The `_geo` attribute must be added to both [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) and [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes). Note that MongoDB uses `[longitude, latitude]` order in GeoJSON while Meilisearch uses `lat, lng` order.
### Vector / semantic search
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:**
```json theme={null}
db.collection.aggregate([
{
"$vectorSearch": {
"index": "vector_index",
"path": "embedding",
"queryVector": [0.1, 0.2, 0.3, "..."],
"numCandidates": 100,
"limit": 10
}
}
])
```
**Meilisearch:**
```json theme={null}
{
"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.
| Operation | MongoDB Atlas Search | Meilisearch |
| :------------------ | :-------------------------------------------------- | :------------------------------------------------- |
| Create search index | `db.collection.createSearchIndex()` | `POST /indexes` (automatic) |
| Delete search index | `db.collection.dropSearchIndex()` | `DELETE /indexes/{index_uid}` |
| List search indexes | `db.collection.getSearchIndexes()` | `GET /indexes` |
| Full-text search | `db.collection.aggregate([{ $search: ... }])` | `POST /indexes/{index_uid}/search` |
| Vector search | `db.collection.aggregate([{ $vectorSearch: ... }])` | `POST /indexes/{index_uid}/search` (with `hybrid`) |
| Facet search | `db.collection.aggregate([{ $searchMeta: ... }])` | `POST /indexes/{index_uid}/search` (with `facets`) |
| Multi-search | Multiple aggregation pipelines | `POST /multi-search` |
| Add documents | `db.collection.insertMany()` | `POST /indexes/{index_uid}/documents` |
| Get document | `db.collection.findOne()` | `GET /indexes/{index_uid}/documents/{id}` |
| Delete document | `db.collection.deleteOne()` | `DELETE /indexes/{index_uid}/documents/{id}` |
| Delete by filter | `db.collection.deleteMany()` | `POST /indexes/{index_uid}/documents/delete` |
| Update settings | Update search index definition | `PATCH /indexes/{index_uid}/settings` |
| Get settings | `db.collection.getSearchIndexes()` | `GET /indexes/{index_uid}/settings` |
| API keys | Atlas access management | `POST /keys` |
| Health check | Atlas monitoring | `GET /health` |
| Task status | Atlas index build status | `GET /tasks/{task_uid}` |
## Front-end components
MongoDB Atlas Search does not include dedicated front-end search components. Meilisearch is compatible with Algolia's [InstantSearch](https://github.com/algolia/instantsearch.js) libraries through [Instant Meilisearch](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/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](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/instant-meilisearch#-api-resources) in the GitHub project's README.
# Migrating from PostgreSQL full-text search to Meilisearch
Source: https://www.meilisearch.com/docs/resources/migration/postgresql_migration
A step-by-step guide to moving search from PostgreSQL (tsvector/tsquery and pgvector) to Meilisearch, with a comparison of settings, queries, and search features.
This page aims to help PostgreSQL users who rely on built-in full-text search (`tsvector`/`tsquery`) and/or the pgvector extension move their search workload to Meilisearch.
For a high-level comparison of the two, see [Meilisearch vs PostgreSQL](/docs/resources/comparisons/postgresql).
## Overview
Meilisearch is not a database replacement. It is a dedicated search engine designed to sit alongside PostgreSQL. The recommended pattern is to keep PostgreSQL as your source of truth and sync data to Meilisearch for search.
This guide walks you through exporting rows from a PostgreSQL table and importing them into Meilisearch using a script in JavaScript, Python, or Ruby. [You can also skip directly to the finished script](#finished-script).
The migration process consists of four steps:
1. [Export your data from PostgreSQL](#export-your-postgresql-data)
2. [Prepare your data for Meilisearch](#prepare-your-data)
3. [Import your data into Meilisearch](#import-your-data-into-meilisearch)
4. [Configure your Meilisearch index settings (optional)](#configure-your-index-settings)
To help with the transition, this guide also includes a comparison of [settings and parameters](#settings-and-parameters-comparison), [query types](#query-comparison), and practical advice for [keeping data in sync](#keeping-data-in-sync).
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](/docs/resources/self_hosting/getting_started/quick_start).
This guide includes examples in JavaScript, Python, and Ruby. The packages used:
* **JavaScript**: [`pg`](https://www.npmjs.com/package/pg) (node-postgres), [`meilisearch`](https://www.npmjs.com/package/meilisearch) (compatible with Meilisearch v1.0+)
* **Python**: [`psycopg2`](https://pypi.org/project/psycopg2/), [`meilisearch`](https://pypi.org/project/meilisearch/)
* **Ruby**: [`pg`](https://rubygems.org/gems/pg), [`meilisearch`](https://rubygems.org/gems/meilisearch)
## Export your PostgreSQL data
### Initialize project
```bash JavaScript theme={null}
mkdir pg-meilisearch-migration
cd pg-meilisearch-migration
npm init -y
touch script.js
```
```bash Python theme={null}
mkdir pg-meilisearch-migration
cd pg-meilisearch-migration
touch script.py
```
```bash Ruby theme={null}
mkdir pg-meilisearch-migration
cd pg-meilisearch-migration
touch script.rb
```
### Install dependencies
```bash JavaScript theme={null}
npm install -s pg meilisearch
```
```bash Python theme={null}
pip install psycopg2-binary meilisearch
```
```bash Ruby theme={null}
gem install pg meilisearch
```
### Create PostgreSQL client
You need your PostgreSQL **connection string** or individual connection parameters (host, database, user, password). Paste the below code in your script:
```javascript JavaScript theme={null}
const { Pool } = require("pg");
const pool = new Pool({
host: "PG_HOST",
port: 5432,
database: "PG_DATABASE",
user: "PG_USER",
password: "PG_PASSWORD",
});
```
```python Python theme={null}
import psycopg2
pg_conn = psycopg2.connect(
host="PG_HOST",
port=5432,
dbname="PG_DATABASE",
user="PG_USER",
password="PG_PASSWORD",
)
```
```ruby Ruby theme={null}
require 'pg'
pg_conn = PG.connect(
host: 'PG_HOST',
port: 5432,
dbname: 'PG_DATABASE',
user: 'PG_USER',
password: 'PG_PASSWORD'
)
```
Replace the placeholder values with your PostgreSQL connection details.
### Fetch data from PostgreSQL
Query your table to retrieve all rows. For large tables, use cursor-based pagination to avoid loading everything into memory at once.
```javascript JavaScript theme={null}
const TABLE_NAME = "YOUR_TABLE_NAME";
const BATCH_SIZE = 10000;
async function fetchAllRows() {
const records = [];
let offset = 0;
while (true) {
const result = await pool.query(
`SELECT * FROM ${TABLE_NAME} ORDER BY id LIMIT $1 OFFSET $2`,
[BATCH_SIZE, offset]
);
if (result.rows.length === 0) break;
records.push(...result.rows);
offset += result.rows.length;
}
return records;
}
```
```python Python theme={null}
TABLE_NAME = "YOUR_TABLE_NAME"
BATCH_SIZE = 10000
def fetch_all_rows():
records = []
offset = 0
cursor = pg_conn.cursor()
while True:
cursor.execute(
f"SELECT * FROM {TABLE_NAME} ORDER BY id LIMIT %s OFFSET %s",
(BATCH_SIZE, offset),
)
rows = cursor.fetchall()
if not rows:
break
# Get column names from cursor description
columns = [desc[0] for desc in cursor.description]
for row in rows:
records.append(dict(zip(columns, row)))
offset += len(rows)
cursor.close()
return records
```
```ruby Ruby theme={null}
TABLE_NAME = 'YOUR_TABLE_NAME'
BATCH_SIZE = 10_000
def fetch_all_rows(pg_conn)
records = []
offset = 0
loop do
result = pg_conn.exec_params(
"SELECT * FROM #{TABLE_NAME} ORDER BY id LIMIT $1 OFFSET $2",
[BATCH_SIZE, offset]
)
break if result.ntuples.zero?
result.each do |row|
records << row
end
offset += result.ntuples
end
records
end
```
Replace `YOUR_TABLE_NAME` with the name of the table you want to migrate. If your table does not have an `id` column, replace it with your primary key column name.
For very large tables (millions of rows), consider using a server-side cursor or `COPY` command to export data to a JSON file, then import that file into Meilisearch.
## Prepare your data
PostgreSQL rows are already flat key-value pairs, so they map naturally to Meilisearch documents. You mainly need to ensure a primary key field exists and convert any PostgreSQL-specific types.
```javascript JavaScript theme={null}
function prepareDocuments(rows) {
return rows.map((row) => {
const doc = { ...row };
// Ensure the primary key is a string named "id"
if (doc.id === undefined && doc.your_pk_column !== undefined) {
doc.id = String(doc.your_pk_column);
} else {
doc.id = String(doc.id);
}
// Convert Date objects to ISO strings
for (const [key, value] of Object.entries(doc)) {
if (value instanceof Date) {
doc[key] = value.toISOString();
}
}
return doc;
});
}
```
```python Python theme={null}
from datetime import date, datetime
from decimal import Decimal
def prepare_documents(rows):
documents = []
for row in rows:
doc = {**row}
# Ensure the primary key is a string named "id"
if "id" not in doc and "your_pk_column" in doc:
doc["id"] = str(doc["your_pk_column"])
else:
doc["id"] = str(doc["id"])
# Convert Python types to JSON-compatible types
for key, value in doc.items():
if isinstance(value, (date, datetime)):
doc[key] = value.isoformat()
elif isinstance(value, Decimal):
doc[key] = float(value)
documents.append(doc)
return documents
```
```ruby Ruby theme={null}
require 'json'
def prepare_documents(rows)
rows.map do |row|
doc = row.dup
# Ensure the primary key is a string named "id"
if doc['id'].nil? && doc['your_pk_column']
doc['id'] = doc['your_pk_column'].to_s
else
doc['id'] = doc['id'].to_s
end
doc
end
end
```
If your primary key column is not called `id`, you can either rename it in the preparation step (as shown above) or tell Meilisearch which field to use as the primary key when creating the index. Replace `your_pk_column` with the actual column name.
### Handle PostGIS geo data
If your table uses PostGIS geography or geometry columns, convert them to Meilisearch's `_geo` format. Export the coordinates from PostgreSQL using `ST_Y()` (latitude) and `ST_X()` (longitude):
```javascript JavaScript theme={null}
// When querying, extract lat/lng from PostGIS:
// SELECT *, ST_Y(location::geometry) AS lat, ST_X(location::geometry) AS lng FROM your_table
function convertGeoFields(doc) {
if (doc.lat !== undefined && doc.lng !== undefined) {
doc._geo = {
lat: parseFloat(doc.lat),
lng: parseFloat(doc.lng),
};
delete doc.lat;
delete doc.lng;
}
// Remove the original PostGIS column if present
delete doc.location;
return doc;
}
```
```python Python theme={null}
# When querying, extract lat/lng from PostGIS:
# SELECT *, ST_Y(location::geometry) AS lat, ST_X(location::geometry) AS lng FROM your_table
def convert_geo_fields(doc):
if "lat" in doc and "lng" in doc:
doc["_geo"] = {
"lat": float(doc["lat"]),
"lng": float(doc["lng"]),
}
del doc["lat"]
del doc["lng"]
# Remove the original PostGIS column if present
doc.pop("location", None)
return doc
```
```ruby Ruby theme={null}
# When querying, extract lat/lng from PostGIS:
# SELECT *, ST_Y(location::geometry) AS lat, ST_X(location::geometry) AS lng FROM your_table
def convert_geo_fields(doc)
if doc['lat'] && doc['lng']
doc['_geo'] = {
'lat' => doc['lat'].to_f,
'lng' => doc['lng'].to_f
}
doc.delete('lat')
doc.delete('lng')
end
# Remove the original PostGIS column if present
doc.delete('location')
doc
end
```
## 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](/docs/resources/self_hosting/security/basic_security).
```javascript JavaScript theme={null}
const { Meilisearch } = require("meilisearch");
const meiliClient = new Meilisearch({
host: "MEILI_HOST",
apiKey: "MEILI_API_KEY",
});
const meiliIndex = meiliClient.index("MEILI_INDEX_NAME");
```
```python Python theme={null}
import meilisearch
meili_client = meilisearch.Client("MEILI_HOST", "MEILI_API_KEY")
meili_index = meili_client.index("MEILI_INDEX_NAME")
```
```ruby Ruby theme={null}
require 'meilisearch'
meili_client = MeiliSearch::Client.new('MEILI_HOST', 'MEILI_API_KEY')
meili_index = meili_client.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.
```javascript JavaScript theme={null}
const UPLOAD_BATCH_SIZE = 100000;
await meiliIndex.addDocumentsInBatches(documents, UPLOAD_BATCH_SIZE);
```
```python Python theme={null}
UPLOAD_BATCH_SIZE = 100000
meili_index.add_documents_in_batches(documents, batch_size=UPLOAD_BATCH_SIZE)
```
```ruby Ruby theme={null}
UPLOAD_BATCH_SIZE = 100_000
meili_index.add_documents_in_batches(documents, UPLOAD_BATCH_SIZE)
```
When you're ready, run the script:
```bash JavaScript theme={null}
node script.js
```
```bash Python theme={null}
python script.py
```
```bash Ruby theme={null}
ruby script.rb
```
### Finished script
```javascript JavaScript theme={null}
const { Pool } = require("pg");
const { Meilisearch } = require("meilisearch");
const TABLE_NAME = "YOUR_TABLE_NAME";
const FETCH_BATCH_SIZE = 10000;
const UPLOAD_BATCH_SIZE = 100000;
(async () => {
// Connect to PostgreSQL
const pool = new Pool({
host: "PG_HOST",
port: 5432,
database: "PG_DATABASE",
user: "PG_USER",
password: "PG_PASSWORD",
});
// Fetch all rows
const records = [];
let offset = 0;
while (true) {
const result = await pool.query(
`SELECT * FROM ${TABLE_NAME} ORDER BY id LIMIT $1 OFFSET $2`,
[FETCH_BATCH_SIZE, offset]
);
if (result.rows.length === 0) break;
records.push(...result.rows);
offset += result.rows.length;
}
await pool.end();
// Prepare documents for Meilisearch
const documents = records.map((row) => {
const doc = { ...row };
doc.id = String(doc.id);
for (const [key, value] of Object.entries(doc)) {
if (value instanceof Date) {
doc[key] = value.toISOString();
}
}
return doc;
});
console.log(`Fetched ${documents.length} rows from PostgreSQL`);
// 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");
})();
```
```python Python theme={null}
import psycopg2
import meilisearch
from datetime import date, datetime
from decimal import Decimal
TABLE_NAME = "YOUR_TABLE_NAME"
FETCH_BATCH_SIZE = 10000
UPLOAD_BATCH_SIZE = 100000
# Connect to PostgreSQL
pg_conn = psycopg2.connect(
host="PG_HOST",
port=5432,
dbname="PG_DATABASE",
user="PG_USER",
password="PG_PASSWORD",
)
# Fetch all rows
records = []
offset = 0
cursor = pg_conn.cursor()
while True:
cursor.execute(
f"SELECT * FROM {TABLE_NAME} ORDER BY id LIMIT %s OFFSET %s",
(FETCH_BATCH_SIZE, offset),
)
rows = cursor.fetchall()
if not rows:
break
columns = [desc[0] for desc in cursor.description]
for row in rows:
records.append(dict(zip(columns, row)))
offset += len(rows)
cursor.close()
pg_conn.close()
# Prepare documents for Meilisearch
documents = []
for row in records:
doc = {**row}
doc["id"] = str(doc["id"])
for key, value in doc.items():
if isinstance(value, (date, datetime)):
doc[key] = value.isoformat()
elif isinstance(value, Decimal):
doc[key] = float(value)
documents.append(doc)
print(f"Fetched {len(documents)} rows from PostgreSQL")
# Upload to Meilisearch
meili_client = meilisearch.Client("MEILI_HOST", "MEILI_API_KEY")
meili_index = meili_client.index("MEILI_INDEX_NAME")
meili_index.add_documents_in_batches(documents, batch_size=UPLOAD_BATCH_SIZE)
print("Migration complete")
```
```ruby Ruby theme={null}
require 'pg'
require 'meilisearch'
TABLE_NAME = 'YOUR_TABLE_NAME'
FETCH_BATCH_SIZE = 10_000
UPLOAD_BATCH_SIZE = 100_000
# Connect to PostgreSQL
pg_conn = PG.connect(
host: 'PG_HOST',
port: 5432,
dbname: 'PG_DATABASE',
user: 'PG_USER',
password: 'PG_PASSWORD'
)
# Fetch all rows
records = []
offset = 0
loop do
result = pg_conn.exec_params(
"SELECT * FROM #{TABLE_NAME} ORDER BY id LIMIT $1 OFFSET $2",
[FETCH_BATCH_SIZE, offset]
)
break if result.ntuples.zero?
result.each { |row| records << row }
offset += result.ntuples
end
pg_conn.close
# Prepare documents for Meilisearch
documents = records.map do |row|
doc = row.dup
doc['id'] = doc['id'].to_s
doc
end
puts "Fetched #{documents.length} rows from PostgreSQL"
# Upload to Meilisearch
meili_client = MeiliSearch::Client.new('MEILI_HOST', 'MEILI_API_KEY')
meili_index = meili_client.index('MEILI_INDEX_NAME')
meili_index.add_documents_in_batches(documents, UPLOAD_BATCH_SIZE)
puts 'Migration complete'
```
## Configure your index settings
Meilisearch's default settings deliver relevant, typo-tolerant search out of the box. Unlike PostgreSQL, where you must create `tsvector` columns, build GIN indexes, and construct queries with `to_tsquery()`, Meilisearch indexes all fields automatically and handles tokenization, stemming, and typo tolerance without any configuration.
To customize your index settings, see [configuring index settings](/docs/resources/internals/indexes#index-settings). To understand the differences between PostgreSQL full-text search and Meilisearch, read on.
### Key conceptual differences
**PostgreSQL full-text search** requires you to manage every aspect of the search pipeline manually. You must create `tsvector` columns (or expressions), build GIN indexes for performance, choose language configurations for stemming and stop words, construct queries with `to_tsquery()` or `plainto_tsquery()`, and rank results with `ts_rank()`. Search is tightly coupled to your database, competing for the same resources as your transactional queries.
**Meilisearch** is a dedicated search engine. You send documents and search queries, and everything else is automatic. Tokenization, stemming, typo tolerance, prefix search, and ranking all work out of the box. Because Meilisearch runs as a separate service, search queries never impact your database performance.
The most important difference: **PostgreSQL has no typo tolerance**. A search for "reciepe" returns zero results even if your table contains hundreds of recipes. Meilisearch handles typos automatically, making it dramatically more forgiving for end users.
### Configure embedders for hybrid search
If you currently use pgvector for semantic similarity search, you can replace it with Meilisearch's built-in hybrid search. Configure an [embedder](/docs/capabilities/hybrid_search/getting_started) and Meilisearch handles all vectorization automatically, both at indexing time and at search time. No more managing embeddings in your application code.
```bash theme={null}
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. For more options including HuggingFace models, Ollama, and custom REST endpoints, see [configuring embedders](/docs/capabilities/hybrid_search/getting_started).
If you already have embeddings stored in a pgvector `vector` column and prefer not to re-embed, export them from PostgreSQL and include them in the `_vectors` field of each document. Then configure a `userProvided` embedder:
```bash theme={null}
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 pgvector embeddings. With this approach, you remain responsible for computing and providing vectors when adding or updating documents, and for computing query vectors client-side when searching.
### Configure filterable and sortable attributes
In PostgreSQL, any column can be used in `WHERE` and `ORDER BY` clauses. In Meilisearch, you must declare which fields are [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) and [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes):
```bash theme={null}
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", "status", "price", "_geo"],
"sortableAttributes": ["price", "created_at", "_geo"]
}'
```
### What you gain
Migrating your search layer from PostgreSQL to Meilisearch gives you several features that work out of the box:
* **Typo tolerance**: PostgreSQL full-text search has none. A single typo returns zero results. Meilisearch handles typos automatically, so "reciepe" finds "recipe"
* **Prefix search**: Users see results as they type, without needing `LIKE 'term%'` queries or trigram indexes
* **Instant results**: Sub-50ms search responses regardless of dataset complexity, with no GIN index tuning or query plan optimization
* **Highlighting** of matching terms in results, without manually calling `ts_headline()`
* **Faceted search** with value distributions for building filter UIs, no `GROUP BY` queries needed
* **Hybrid search** combining keyword relevancy and semantic similarity in a single query, replacing separate pgvector and `tsvector` pipelines
* **No search infrastructure in your database**: Remove `tsvector` columns, GIN indexes, triggers, and ranking functions. Your PostgreSQL database handles what it does best (transactions and relational data), while Meilisearch handles search
## Settings and parameters comparison
### Text search configuration
| PostgreSQL | Meilisearch | Notes |
| :-------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------- |
| `tsvector` column + GIN index | Automatic | Meilisearch indexes all fields automatically, no columns or indexes to create |
| `to_tsvector(config, text)` | Automatic tokenization | No text processing functions needed |
| `ts_rank()` / `ts_rank_cd()` | Built-in [ranking rules](/docs/reference/api/settings/update-ranking-rules) | Relevancy ranking is automatic and configurable |
| Language configurations (`english`, `french`, etc.) | [`localizedAttributes`](/docs/reference/api/settings/update-localizedattributes) | Assign languages to specific fields |
| `setweight()` (A, B, C, D) | [`searchableAttributes`](/docs/reference/api/settings/update-searchableattributes) | Ordered list, fields listed first have higher priority |
| Custom dictionaries | [`synonyms`](/docs/reference/api/settings/update-synonyms) / [`stopWords`](/docs/reference/api/settings/update-stopwords) | Configure equivalent terms and ignored words |
| `tsvector` update triggers | Automatic | Meilisearch re-indexes on every document update |
### Search queries
| PostgreSQL | Meilisearch | Notes |
| :-------------------------------------------------------------- | :----------------------------------------------------------------------- | :------------------------------------------------------------------------------------- |
| `to_tsquery()` / `plainto_tsquery()` / `websearch_to_tsquery()` | `q` search param | Just send the user's text, no query construction needed |
| `@@` operator | Automatic | No matching operator needed, `q` handles it |
| `WHERE column = value` | `filter` search param | Requires [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) |
| `ORDER BY column` | `sort` search param | Requires [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes) |
| `LIMIT` / `OFFSET` | `limit` / `offset` or `page` / `hitsPerPage` | Search params |
| `ts_headline()` | `attributesToHighlight` | Search param, returns highlighted snippets automatically |
| `COUNT(*)` | `estimatedTotalHits` / `totalHits` | Returned in every search response |
| `ILIKE '%term%'` | `q` with prefix search | Automatic prefix matching on the last word |
| No typo tolerance | Automatic [typo tolerance](/docs/reference/api/settings/update-typotolerance) | Configurable per index |
### Vector search (pgvector)
| PostgreSQL (pgvector) | Meilisearch | Notes |
| :---------------------------------------------- | :-------------------------------------------------------------- | :-------------------------------------------------------------------- |
| `ORDER BY embedding <=> query_vector` (cosine) | `hybrid` + auto-embedder | Meilisearch embeds queries for you, no client-side vector computation |
| `ORDER BY embedding <-> query_vector` (L2) | `hybrid` + auto-embedder | Distance metric is handled automatically |
| `vector` type + `ivfflat` / `hnsw` index | [`embedders`](/docs/reference/api/settings/update-embedders) setting | Automatic indexing (DiskANN-based), no index type selection needed |
| Manual embedding generation in application code | Automatic via configured embedder | Meilisearch embeds documents and queries for you |
| Separate keyword + vector queries | Single `hybrid` query | Combines keyword and semantic search in one request |
### Geo search (PostGIS)
| PostgreSQL (PostGIS) | Meilisearch | Notes |
| :--------------------------------------------------- | :---------------------------------------------------- | :---------------------------------------- |
| `ST_DWithin(geog, ST_MakePoint(lng, lat), distance)` | `_geoRadius(lat, lng, distance)` in `filter` | Requires `_geo` in `filterableAttributes` |
| `ST_MakeEnvelope(xmin, ymin, xmax, ymax)` | `_geoBoundingBox([lat, lng], [lat, lng])` in `filter` | Requires `_geo` in `filterableAttributes` |
| `ORDER BY ST_Distance(geog, point)` | `_geoPoint(lat, lng):asc` in `sort` | Requires `_geo` in `sortableAttributes` |
| `geography` / `geometry` types | `_geo` field with `lat` / `lng` | Simple JSON object |
## Query comparison
This section shows how common PostgreSQL search queries translate to Meilisearch.
### Full-text search
**PostgreSQL:**
```sql theme={null}
SELECT * FROM products
WHERE to_tsvector('english', title || ' ' || description) @@ plainto_tsquery('english', 'running shoes')
ORDER BY ts_rank(to_tsvector('english', title || ' ' || description), plainto_tsquery('english', 'running shoes')) DESC
LIMIT 20;
```
**Meilisearch:**
```json theme={null}
POST /indexes/products/search
{
"q": "running shoes",
"limit": 20
}
```
No `tsvector` columns, no `@@` operator, no `ts_rank()` function. Just send the text.
### Filtered search
**PostgreSQL:**
```sql theme={null}
SELECT * FROM products
WHERE to_tsvector('english', title) @@ plainto_tsquery('english', 'laptop')
AND category = 'electronics'
AND price BETWEEN 500 AND 1500
ORDER BY ts_rank(to_tsvector('english', title), plainto_tsquery('english', 'laptop')) DESC;
```
**Meilisearch:**
```json theme={null}
POST /indexes/products/search
{
"q": "laptop",
"filter": "category = electronics AND price >= 500 AND price <= 1500"
}
```
Attributes used in `filter` must first be added to [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes).
### Sorting
**PostgreSQL:**
```sql theme={null}
SELECT * FROM products
WHERE to_tsvector('english', title) @@ plainto_tsquery('english', 'shoes')
ORDER BY price ASC, created_at DESC;
```
**Meilisearch:**
```json theme={null}
POST /indexes/products/search
{
"q": "shoes",
"sort": ["price:asc", "created_at:desc"]
}
```
Attributes used in `sort` must first be added to [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes).
### Highlighting
**PostgreSQL:**
```sql theme={null}
SELECT id, ts_headline('english', description, plainto_tsquery('english', 'chocolate cake'),
'StartSel=, StopSel=, MaxFragments=2')
FROM recipes
WHERE to_tsvector('english', description) @@ plainto_tsquery('english', 'chocolate cake');
```
**Meilisearch:**
```json theme={null}
POST /indexes/recipes/search
{
"q": "chocolate cake",
"attributesToHighlight": ["description"],
"highlightPreTag": "",
"highlightPostTag": ""
}
```
### Geo search
**PostgreSQL (PostGIS):**
```sql theme={null}
SELECT *, ST_Distance(location, ST_MakePoint(2.3522, 48.8566)::geography) AS distance
FROM restaurants
WHERE ST_DWithin(location, ST_MakePoint(2.3522, 48.8566)::geography, 5000)
ORDER BY distance ASC;
```
**Meilisearch:**
```json theme={null}
POST /indexes/restaurants/search
{
"filter": "_geoRadius(48.8566, 2.3522, 5000)",
"sort": ["_geoPoint(48.8566, 2.3522):asc"]
}
```
The `_geo` attribute must be added to both [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) and [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes).
### Semantic search
**PostgreSQL (pgvector):**
```sql theme={null}
-- Application must first compute the query embedding
-- query_embedding = openai.embed("comfortable running shoes")
SELECT * FROM products
ORDER BY embedding <=> '[0.1, 0.2, 0.3, ...]'::vector
LIMIT 10;
```
**Meilisearch:**
```json theme={null}
POST /indexes/products/search
{
"q": "comfortable running shoes",
"hybrid": {
"semanticRatio": 1.0,
"embedder": "default"
},
"limit": 10
}
```
With an auto-embedder configured, Meilisearch embeds the `q` text for you. No client-side vector computation. Setting `semanticRatio` to `1.0` performs pure semantic search. Use a value like `0.5` to combine keyword and semantic results in a single hybrid query.
### Faceted search
**PostgreSQL:**
```sql theme={null}
SELECT category, COUNT(*) as count
FROM products
WHERE to_tsvector('english', title) @@ plainto_tsquery('english', 'shoes')
GROUP BY category
ORDER BY count DESC;
```
**Meilisearch:**
```json theme={null}
POST /indexes/products/search
{
"q": "shoes",
"facets": ["category", "brand", "color"]
}
```
Meilisearch returns search results and value distributions for all requested facets in a single response, no separate `GROUP BY` queries needed.
## Keeping data in sync
Since PostgreSQL remains your source of truth, you need a strategy to keep Meilisearch in sync when data changes. Common approaches:
* **Application-level sync**: After every INSERT, UPDATE, or DELETE in your application code, send the corresponding change to Meilisearch. This is the simplest approach and works well for most applications
* **Database triggers with notifications**: Use PostgreSQL `LISTEN`/`NOTIFY` to broadcast changes, then have a worker process consume notifications and update Meilisearch
* **Periodic batch sync**: Run a scheduled job (every few minutes) that queries PostgreSQL for recently modified rows (using an `updated_at` timestamp) and sends them to Meilisearch
* **Change data capture (CDC)**: Use tools like Debezium to stream PostgreSQL WAL changes to Meilisearch in near real-time
For most applications, application-level sync provides the best balance of simplicity and freshness. Meilisearch's `addDocuments` method is an upsert: sending an existing document with the same primary key updates it automatically.
## Front-end components
PostgreSQL does not include front-end search components. Meilisearch is compatible with Algolia's [InstantSearch](https://github.com/algolia/instantsearch.js) libraries through [Instant Meilisearch](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/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](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/instant-meilisearch#-api-resources) in the GitHub project's README.
# Migrating from Qdrant to Meilisearch
Source: https://www.meilisearch.com/docs/resources/migration/qdrant_migration
A step-by-step guide to exporting data from Qdrant and importing it into Meilisearch, with a comparison of settings, queries, and API methods.
This page aims to help current users of Qdrant make the transition to Meilisearch.
## Overview
Qdrant is a vector similarity search engine. Meilisearch combines full-text search with vector search through its [hybrid search](/docs/capabilities/hybrid_search/getting_started) feature, letting you replace a separate keyword search engine and vector database with a single system.
This guide walks you through exporting points from a Qdrant collection and importing them into Meilisearch using a script in JavaScript, Python, or Ruby. [You can also skip directly to the finished script](#finished-script).
The migration process consists of four steps:
1. [Export your data from Qdrant](#export-your-qdrant-data)
2. [Prepare your data for Meilisearch](#prepare-your-data)
3. [Import your data into Meilisearch](#import-your-data-into-meilisearch)
4. [Configure embedders and index settings](#configure-your-index-settings)
To help with the transition, this guide also includes a comparison of [settings and parameters](#settings-and-parameters-comparison), [query types](#query-comparison), and [API methods](#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](/docs/resources/self_hosting/getting_started/quick_start).
This guide includes examples in JavaScript, Python, and Ruby. The packages used:
* **JavaScript**: [`@qdrant/js-client-rest`](https://www.npmjs.com/package/@qdrant/js-client-rest) `1.x`, [`meilisearch`](https://www.npmjs.com/package/meilisearch) (compatible with Meilisearch v1.0+)
* **Python**: [`qdrant-client`](https://pypi.org/project/qdrant-client/) `1.x`, [`meilisearch`](https://pypi.org/project/meilisearch/)
* **Ruby**: [`qdrant-ruby`](https://rubygems.org/gems/qdrant-ruby), [`meilisearch`](https://rubygems.org/gems/meilisearch)
## Export your Qdrant data
### Initialize project
```bash JavaScript theme={null}
mkdir qdrant-meilisearch-migration
cd qdrant-meilisearch-migration
npm init -y
touch script.js
```
```bash Python theme={null}
mkdir qdrant-meilisearch-migration
cd qdrant-meilisearch-migration
touch script.py
```
```bash Ruby theme={null}
mkdir qdrant-meilisearch-migration
cd qdrant-meilisearch-migration
touch script.rb
```
### Install dependencies
```bash JavaScript theme={null}
npm install -s @qdrant/js-client-rest meilisearch
```
```bash Python theme={null}
pip install qdrant-client meilisearch
```
```bash Ruby theme={null}
gem install qdrant-ruby meilisearch
```
### Create Qdrant client
You need your Qdrant **host URL** and optionally an **API key** if your instance requires authentication.
```javascript JavaScript theme={null}
const { QdrantClient } = require("@qdrant/js-client-rest");
const qdrantClient = new QdrantClient({
url: "QDRANT_URL",
// apiKey: "QDRANT_API_KEY", // if authentication is enabled
});
```
```python Python theme={null}
from qdrant_client import QdrantClient
qdrant_client = QdrantClient(
url="QDRANT_URL",
# api_key="QDRANT_API_KEY", # if authentication is enabled
)
```
```ruby Ruby theme={null}
require 'qdrant'
qdrant_client = Qdrant::Client.new(
url: 'QDRANT_URL'
# api_key: 'QDRANT_API_KEY' # if authentication is enabled
)
```
Replace `QDRANT_URL` with your Qdrant instance URL (for example, `http://localhost:6333`) and provide your API key if required.
### Fetch data from Qdrant
Use the [Scroll API](https://qdrant.tech/documentation/concepts/points/#scroll-points) to paginate through all points in a collection. This retrieves both payload data and vectors.
```javascript JavaScript theme={null}
const COLLECTION_NAME = "YOUR_COLLECTION_NAME";
const BATCH_SIZE = 1000;
async function fetchAllPoints() {
const records = [];
let offset = null;
while (true) {
const response = await qdrantClient.scroll(COLLECTION_NAME, {
limit: BATCH_SIZE,
offset: offset,
with_payload: true,
with_vectors: true,
});
records.push(...response.points);
if (!response.next_page_offset) break;
offset = response.next_page_offset;
}
return records;
}
```
```python Python theme={null}
COLLECTION_NAME = "YOUR_COLLECTION_NAME"
BATCH_SIZE = 1000
def fetch_all_points():
records = []
offset = None
while True:
response = qdrant_client.scroll(
collection_name=COLLECTION_NAME,
limit=BATCH_SIZE,
offset=offset,
with_payload=True,
with_vectors=True,
)
points, next_offset = response
records.extend(points)
if next_offset is None:
break
offset = next_offset
return records
```
```ruby Ruby theme={null}
COLLECTION_NAME = 'YOUR_COLLECTION_NAME'
BATCH_SIZE = 1000
def fetch_all_points(qdrant_client)
records = []
offset = nil
loop do
response = qdrant_client.points.scroll(
collection_name: COLLECTION_NAME,
limit: BATCH_SIZE,
offset: offset,
with_payload: true,
with_vectors: true
)
points = response.dig('result', 'points') || []
records.concat(points)
next_offset = response.dig('result', 'next_page_offset')
break if next_offset.nil?
offset = next_offset
end
records
end
```
Replace `YOUR_COLLECTION_NAME` with the name of the Qdrant collection you want to migrate.
Set `with_vectors: true` if you want to keep your existing vectors. If you plan to let Meilisearch re-embed your documents using a configured embedder, you can set this to `false` to speed up the export.
## Prepare your data
Qdrant points contain an `id`, a `payload` (key-value data), and one or more `vectors`. You need to extract the payload fields as top-level document fields for Meilisearch.
### Choose your vector strategy
Before preparing your data, decide how you want to handle vectors:
* **Option A: Let Meilisearch re-embed** (recommended): Configure an [embedder](/docs/capabilities/hybrid_search/getting_started) in Meilisearch and let it generate vectors automatically from your document content. This is simpler and keeps your vectors in sync with your data.
* **Option B: Keep existing vectors**: Include your Qdrant vectors in the `_vectors` field of each document using a `userProvided` embedder. This avoids re-embedding costs but requires you to manage vector updates yourself.
### Transform documents
```javascript JavaScript theme={null}
function prepareDocuments(points, keepVectors = false) {
return points.map((point) => {
// Extract payload fields as top-level document fields
const doc = { ...point.payload };
doc.id = String(point.id);
// Option B: keep existing vectors
if (keepVectors && point.vector) {
if (typeof point.vector === "object" && !Array.isArray(point.vector)) {
// Named vectors: { "text-embedding": [...], "image-embedding": [...] }
doc._vectors = point.vector;
} else {
// Single unnamed vector
doc._vectors = { default: point.vector };
}
}
return doc;
});
}
```
```python Python theme={null}
def prepare_documents(points, keep_vectors=False):
documents = []
for point in points:
# Extract payload fields as top-level document fields
doc = {**point.payload}
doc["id"] = str(point.id)
# Option B: keep existing vectors
if keep_vectors and point.vector is not None:
if isinstance(point.vector, dict):
# Named vectors: { "text-embedding": [...], "image-embedding": [...] }
doc["_vectors"] = point.vector
else:
# Single unnamed vector
doc["_vectors"] = {"default": point.vector}
documents.append(doc)
return documents
```
```ruby Ruby theme={null}
def prepare_documents(points, keep_vectors: false)
points.map do |point|
payload = point.is_a?(Hash) ? point['payload'] : point.payload
vector = point.is_a?(Hash) ? point['vector'] : point.vector
point_id = point.is_a?(Hash) ? point['id'] : point.id
# Extract payload fields as top-level document fields
doc = payload.dup
doc['id'] = point_id.to_s
# Option B: keep existing vectors
if keep_vectors && vector
if vector.is_a?(Hash)
# Named vectors: { "text-embedding" => [...], "image-embedding" => [...] }
doc['_vectors'] = vector
else
# Single unnamed vector
doc['_vectors'] = { 'default' => vector }
end
end
doc
end
end
```
### Handle geo data
If your Qdrant payloads contain `geo` fields (objects with `lat` and `lon`), convert them to Meilisearch's `_geo` format:
```javascript JavaScript theme={null}
function convertGeoFields(doc, geoFieldName) {
if (doc[geoFieldName]) {
const geo = doc[geoFieldName];
doc._geo = {
lat: geo.lat,
lng: geo.lon, // Qdrant uses "lon", Meilisearch uses "lng"
};
delete doc[geoFieldName];
}
return doc;
}
```
```python Python theme={null}
def convert_geo_fields(doc, geo_field_name):
if geo_field_name in doc:
geo = doc[geo_field_name]
doc["_geo"] = {
"lat": geo["lat"],
"lng": geo["lon"], # Qdrant uses "lon", Meilisearch uses "lng"
}
del doc[geo_field_name]
return doc
```
```ruby Ruby theme={null}
def convert_geo_fields(doc, geo_field_name)
if doc[geo_field_name]
geo = doc[geo_field_name]
doc['_geo'] = {
'lat' => geo['lat'],
'lng' => geo['lon'] # Qdrant uses "lon", Meilisearch uses "lng"
}
doc.delete(geo_field_name)
end
doc
end
```
## 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](/docs/resources/self_hosting/security/basic_security).
```javascript JavaScript theme={null}
const { Meilisearch } = require("meilisearch");
const meiliClient = new Meilisearch({
host: "MEILI_HOST",
apiKey: "MEILI_API_KEY",
});
const meiliIndex = meiliClient.index("MEILI_INDEX_NAME");
```
```python Python theme={null}
import meilisearch
meili_client = meilisearch.Client("MEILI_HOST", "MEILI_API_KEY")
meili_index = meili_client.index("MEILI_INDEX_NAME")
```
```ruby Ruby theme={null}
require 'meilisearch'
meili_client = MeiliSearch::Client.new('MEILI_HOST', 'MEILI_API_KEY')
meili_index = meili_client.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 to upload all records in batches of 100,000.
```javascript JavaScript theme={null}
const UPLOAD_BATCH_SIZE = 100000;
await meiliIndex.addDocumentsInBatches(documents, UPLOAD_BATCH_SIZE);
```
```python Python theme={null}
UPLOAD_BATCH_SIZE = 100000
meili_index.add_documents_in_batches(documents, batch_size=UPLOAD_BATCH_SIZE)
```
```ruby Ruby theme={null}
UPLOAD_BATCH_SIZE = 100000
meili_index.add_documents_in_batches(documents, UPLOAD_BATCH_SIZE)
```
When you're ready, run the script:
```bash JavaScript theme={null}
node script.js
```
```bash Python theme={null}
python script.py
```
```bash Ruby theme={null}
ruby script.rb
```
### Finished script
```javascript JavaScript theme={null}
const { QdrantClient } = require("@qdrant/js-client-rest");
const { Meilisearch } = require("meilisearch");
const COLLECTION_NAME = "YOUR_COLLECTION_NAME";
const FETCH_BATCH_SIZE = 1000;
const UPLOAD_BATCH_SIZE = 100000;
const KEEP_VECTORS = false; // set to true to preserve existing vectors
(async () => {
// Connect to Qdrant
const qdrantClient = new QdrantClient({
url: "QDRANT_URL",
});
// Fetch all points using Scroll API
const records = [];
let offset = null;
while (true) {
const response = await qdrantClient.scroll(COLLECTION_NAME, {
limit: FETCH_BATCH_SIZE,
offset: offset,
with_payload: true,
with_vectors: KEEP_VECTORS,
});
records.push(...response.points);
if (!response.next_page_offset) break;
offset = response.next_page_offset;
}
// Prepare documents for Meilisearch
const documents = records.map((point) => {
const doc = { ...point.payload };
doc.id = String(point.id);
if (KEEP_VECTORS && point.vector) {
if (typeof point.vector === "object" && !Array.isArray(point.vector)) {
doc._vectors = point.vector;
} else {
doc._vectors = { default: point.vector };
}
}
return doc;
});
console.log(`Fetched ${documents.length} points from Qdrant`);
// 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");
})();
```
```python Python theme={null}
from qdrant_client import QdrantClient
import meilisearch
COLLECTION_NAME = "YOUR_COLLECTION_NAME"
FETCH_BATCH_SIZE = 1000
UPLOAD_BATCH_SIZE = 100000
KEEP_VECTORS = False # set to True to preserve existing vectors
# Connect to Qdrant
qdrant_client = QdrantClient(url="QDRANT_URL")
# Fetch all points using Scroll API
records = []
offset = None
while True:
points, next_offset = qdrant_client.scroll(
collection_name=COLLECTION_NAME,
limit=FETCH_BATCH_SIZE,
offset=offset,
with_payload=True,
with_vectors=KEEP_VECTORS,
)
records.extend(points)
if next_offset is None:
break
offset = next_offset
# Prepare documents for Meilisearch
documents = []
for point in records:
doc = {**point.payload}
doc["id"] = str(point.id)
if KEEP_VECTORS and point.vector is not None:
if isinstance(point.vector, dict):
doc["_vectors"] = point.vector
else:
doc["_vectors"] = {"default": point.vector}
documents.append(doc)
print(f"Fetched {len(documents)} points from Qdrant")
# Upload to Meilisearch
meili_client = meilisearch.Client("MEILI_HOST", "MEILI_API_KEY")
meili_index = meili_client.index("MEILI_INDEX_NAME")
meili_index.add_documents_in_batches(documents, batch_size=UPLOAD_BATCH_SIZE)
print("Migration complete")
```
```ruby Ruby theme={null}
require 'qdrant'
require 'meilisearch'
COLLECTION_NAME = 'YOUR_COLLECTION_NAME'
FETCH_BATCH_SIZE = 1000
UPLOAD_BATCH_SIZE = 100_000
KEEP_VECTORS = false # set to true to preserve existing vectors
# Connect to Qdrant
qdrant_client = Qdrant::Client.new(url: 'QDRANT_URL')
# Fetch all points using Scroll API
records = []
offset = nil
loop do
response = qdrant_client.points.scroll(
collection_name: COLLECTION_NAME,
limit: FETCH_BATCH_SIZE,
offset: offset,
with_payload: true,
with_vectors: KEEP_VECTORS
)
points = response.dig('result', 'points') || []
records.concat(points)
next_offset = response.dig('result', 'next_page_offset')
break if next_offset.nil?
offset = next_offset
end
# Prepare documents for Meilisearch
documents = records.map do |point|
doc = point['payload'].dup
doc['id'] = point['id'].to_s
if KEEP_VECTORS && point['vector']
if point['vector'].is_a?(Hash)
doc['_vectors'] = point['vector']
else
doc['_vectors'] = { 'default' => point['vector'] }
end
end
doc
end
puts "Fetched #{documents.length} points from Qdrant"
# Upload to Meilisearch
meili_client = MeiliSearch::Client.new('MEILI_HOST', 'MEILI_API_KEY')
meili_index = meili_client.index('MEILI_INDEX_NAME')
meili_index.add_documents_in_batches(documents, UPLOAD_BATCH_SIZE)
puts 'Migration complete'
```
## Configure your index settings
After importing your data, you need to configure Meilisearch to handle vector search. You also gain access to full-text search, typo tolerance, faceting, and other features that work automatically.
### Configure embedders
One of the biggest differences between Qdrant and Meilisearch is how they handle vectors. With Qdrant, 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 to your search engine, you simply send documents and text queries to Meilisearch.
Configure an [embedder](/docs/capabilities/hybrid_search/getting_started) source such as OpenAI, HuggingFace, or a custom REST endpoint:
```bash theme={null}
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](/docs/capabilities/hybrid_search/getting_started).
If you prefer to keep your existing Qdrant vectors instead of re-embedding, you can export them (set `with_vectors: true` in the migration script) and configure a `userProvided` embedder:
```bash theme={null}
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 vector dimension used in your Qdrant collection. 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.
If your Qdrant collection uses **named vectors**, create a separate embedder for each vector name. The embedder names in Meilisearch must match the keys used in the `_vectors` field of your documents.
### Configure filterable and sortable attributes
In Qdrant, payload indexes must be created explicitly for filtering. In Meilisearch, configure [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) and [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes) for the fields you want to filter and sort on:
```bash theme={null}
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", "_geo"],
"sortableAttributes": ["price", "date", "_geo"]
}'
```
### What you gain
Migrating from Qdrant to Meilisearch gives you several features that work out of the box:
* **No more client-side embedding**: Configure an embedder once, then just send text queries. Meilisearch handles vectorization for both documents and searches
* **Full-text search** with typo tolerance, prefix matching, and language-aware tokenization
* **Hybrid search** combining keyword relevancy and semantic similarity in a single query, with no need to orchestrate two search systems
* **Faceted search** with value distributions for building filter UIs
* **Highlighting** of matching terms in results
* **Synonyms and stop words** support
* **Built-in ranking rules** that combine text relevancy, semantic similarity, and custom sort attributes
### Settings and parameters comparison
The below tables compare Qdrant concepts with their Meilisearch equivalents.
#### Core concepts
| Qdrant | Meilisearch | Notes |
| :------------------------- | :--------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------- |
| Collection | Index | N/A |
| Point | Document | A Point is vector-first (vector + metadata payload). A Document is content-first (fields + optional vectors) |
| Payload | Document fields | Payload fields become top-level document fields |
| Vector | `_vectors` field or auto-generated via [`embedders`](/docs/reference/api/settings/update-embedders) | Meilisearch can auto-generate vectors from document content, so importing vectors is optional |
| Point ID (uuid or integer) | Document `id` (string) | Must convert to string |
| Named vectors | Multiple [`embedders`](/docs/reference/api/settings/update-embedders) | One embedder per vector name |
| Collection aliases | [Index swap](/docs/reference/api/indexes/swap-indexes) | Atomic swap of two indexes |
#### Indexing and storage
| Qdrant | Meilisearch | Notes |
| :------------------------------------- | :------------------------------------------------------------------------------------------------ | :------------------------------------- |
| `payload_schema` / payload index | [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) | Required for filtering |
| HNSW index config | Automatic (DiskANN-based) | No manual tuning needed |
| Quantization (scalar, product, binary) | Built-in binary quantization via [`embedders`](/docs/reference/api/settings/update-embedders) | Configured per embedder |
| `on_disk` storage | Automatic | Meilisearch uses memory-mapped storage |
| Sharding / replication | Automatic ([Meilisearch Cloud](https://www.meilisearch.com/cloud)) | N/A |
| Snapshots | [Dumps](/docs/reference/api/backups/create-dump) / [Snapshots](/docs/reference/api/backups/create-snapshot) | N/A |
#### Search parameters
| Qdrant | Meilisearch | Notes |
| :-------------------------------- | :---------------------------------------------------- | :------------------------------------------------------------------------------- |
| `query` (precomputed vector) | `q` + `hybrid` with auto-embedder | Meilisearch embeds the query for you, no client-side vector computation needed |
| No built-in full-text search | `q` search param | Full-text search with typo tolerance, works standalone or combined with `hybrid` |
| No equivalent | `hybrid.semanticRatio` | Tune the balance between keyword and semantic results (0.0–1.0) |
| `filter.must` | `filter` with `AND` | N/A |
| `filter.should` | `filter` with `OR` | N/A |
| `filter.must_not` | `filter` with `NOT` | N/A |
| `filter.match` (exact value) | `filter` with `=` operator | N/A |
| `filter.range` (gt, gte, lt, lte) | `filter` with `>`, `>=`, `<`, `<=` or `TO` | N/A |
| `filter.geo_bounding_box` | `_geoBoundingBox([lat, lng], [lat, lng])` in `filter` | N/A |
| `filter.geo_radius` | `_geoRadius(lat, lng, radius)` in `filter` | N/A |
| `with_payload` | `attributesToRetrieve` | Search param |
| `score_threshold` | `rankingScoreThreshold` | Search param |
| `limit` | `limit` | Search param |
| `offset` | `offset` | Search param |
| `with_vectors` | `retrieveVectors` | Search param |
| No equivalent | `attributesToHighlight` | Highlight matching terms in results |
| No equivalent | `facets` | Get value distributions for fields |
| No equivalent | `sort` | Sort by attributes (requires `sortableAttributes`) |
| No equivalent | `attributesToCrop` | Excerpt matching content |
## Query comparison
This section shows how common Qdrant queries translate to Meilisearch. All Meilisearch examples below assume you have configured an [auto-embedder](#configure-embedders): you simply send a text query and Meilisearch handles embedding automatically. No need to compute vectors client-side.
### Semantic search
With Qdrant, you must compute the query vector yourself before searching. With Meilisearch, you just send a natural language query:
**Qdrant:**
```json theme={null}
POST /collections/my_collection/points/search
{
"vector": [0.1, 0.2, 0.3, ...],
"limit": 10
}
```
**Meilisearch:**
```json theme={null}
POST /indexes/my_index/search
{
"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, just like Qdrant, but without managing vectors in your application code.
### Hybrid search (keyword + semantic)
This is Meilisearch's biggest advantage over Qdrant. A single query combines typo-tolerant keyword matching with semantic similarity, something that would require two separate systems with Qdrant:
**Meilisearch:**
```json theme={null}
POST /indexes/my_index/search
{
"q": "comfortable running shoes",
"hybrid": {
"semanticRatio": 0.5,
"embedder": "default"
}
}
```
A `semanticRatio` of `0.5` gives equal weight to keyword and semantic results. Adjust this value to tune the balance: closer to `0.0` favors keyword matching, closer to `1.0` favors semantic similarity.
### Filtered search
**Qdrant:**
```json theme={null}
POST /collections/my_collection/points/search
{
"vector": [0.1, 0.2, 0.3, ...],
"filter": {
"must": [
{ "key": "category", "match": { "value": "electronics" } },
{ "key": "price", "range": { "lte": 500 } }
]
},
"limit": 10
}
```
**Meilisearch:**
```json theme={null}
POST /indexes/my_index/search
{
"q": "wireless headphones",
"filter": "category = electronics AND price <= 500",
"hybrid": {
"semanticRatio": 0.7,
"embedder": "default"
},
"limit": 10
}
```
No need to compute a vector for "wireless headphones": Meilisearch handles it. The filter syntax is also simpler: a single string instead of nested JSON objects.
Attributes used in `filter` must first be added to [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes).
### Geo search
**Qdrant:**
```json theme={null}
POST /collections/my_collection/points/search
{
"vector": [0.1, 0.2, 0.3, ...],
"filter": {
"must": [
{
"key": "location",
"geo_radius": {
"center": { "lat": 48.8566, "lon": 2.3522 },
"radius": 10000
}
}
]
}
}
```
**Meilisearch:**
```json theme={null}
POST /indexes/my_index/search
{
"q": "restaurant",
"filter": "_geoRadius(48.8566, 2.3522, 10000)",
"sort": ["_geoPoint(48.8566, 2.3522):asc"],
"hybrid": {
"semanticRatio": 0.5,
"embedder": "default"
}
}
```
Meilisearch adds geo-distance sorting on top of filtered search, and you still just send a text query instead of a precomputed vector.
The `_geo` attribute must be added to both [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) and [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes).
### Faceted search
Qdrant has no equivalent for faceted search. In Meilisearch, you can retrieve value distributions for any filterable attribute:
**Meilisearch:**
```json theme={null}
POST /indexes/my_index/search
{
"q": "shoes",
"facets": ["brand", "color", "size"],
"hybrid": {
"semanticRatio": 0.5,
"embedder": "default"
}
}
```
This returns search results along with a count of documents matching each facet value, useful for building filter UIs.
### Full-text search (no vectors)
Meilisearch also works as a standalone keyword search engine. If you don't need semantic search for a particular query, omit the `hybrid` parameter entirely:
**Meilisearch:**
```json theme={null}
POST /indexes/my_index/search
{
"q": "runnign shoes",
"limit": 10
}
```
This returns results using keyword matching with automatic typo tolerance (note the typo in "runnign", Meilisearch handles it). This has no equivalent in Qdrant.
## API methods
This section compares Qdrant and Meilisearch API operations.
| Operation | Qdrant | Meilisearch |
| :------------------------ | :--------------------------------------------- | :------------------------------------------- |
| Create collection/index | `PUT /collections/{name}` | `POST /indexes` |
| Delete collection/index | `DELETE /collections/{name}` | `DELETE /indexes/{index_uid}` |
| Get collection/index info | `GET /collections/{name}` | `GET /indexes/{index_uid}` |
| List collections/indexes | `GET /collections` | `GET /indexes` |
| Upsert points/documents | `PUT /collections/{name}/points` | `POST /indexes/{index_uid}/documents` |
| Get point/document | `GET /collections/{name}/points/{id}` | `GET /indexes/{index_uid}/documents/{id}` |
| Delete points/documents | `POST /collections/{name}/points/delete` | `POST /indexes/{index_uid}/documents/delete` |
| Scroll/browse | `POST /collections/{name}/points/scroll` | `GET /indexes/{index_uid}/documents` |
| Search | `POST /collections/{name}/points/search` | `POST /indexes/{index_uid}/search` |
| Multi-search | `POST /collections/{name}/points/search/batch` | `POST /multi-search` |
| Create payload index | `PUT /collections/{name}/index` | `PATCH /indexes/{index_uid}/settings` |
| Get collection config | `GET /collections/{name}` | `GET /indexes/{index_uid}/settings` |
| Create snapshot | `POST /collections/{name}/snapshots` | `POST /snapshots` |
| Health check | `GET /healthz` | `GET /health` |
## Front-end components
Qdrant does not include front-end search components. Meilisearch is compatible with Algolia's [InstantSearch](https://github.com/algolia/instantsearch.js) libraries through [Instant Meilisearch](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/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](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/instant-meilisearch#-api-resources) in the GitHub project's README.
# Migrating from Supabase full-text search to Meilisearch
Source: https://www.meilisearch.com/docs/resources/migration/supabase_migration
A step-by-step guide to moving search from Supabase (tsvector/tsquery and pgvector via Supabase Vector) to Meilisearch, with a comparison of settings, queries, and search features.
This page aims to help Supabase users who rely on built-in full-text search (`.textSearch()`) and/or Supabase Vector move their search workload to Meilisearch.
For a high-level comparison with PostgreSQL-based search, see [Meilisearch vs PostgreSQL](/docs/resources/comparisons/postgresql).
## Overview
Meilisearch is not a replacement for Supabase. It is a dedicated search engine designed to sit alongside your Supabase database. The recommended pattern is to keep Supabase as your source of truth and sync data to Meilisearch for search.
Supabase exposes PostgreSQL's built-in `tsvector`/`tsquery` full-text search through its client libraries (`.textSearch()` method) and uses the pgvector extension for vector similarity search (Supabase Vector). While convenient, these inherit all of PostgreSQL's search limitations: no typo tolerance, no prefix search by default, no built-in relevancy ranking, and manual configuration of `tsvector` columns and GIN indexes.
This guide walks you through exporting rows from Supabase and importing them into Meilisearch using a script in JavaScript, Python, or Ruby. [You can also skip directly to the finished script](#finished-script).
The migration process consists of four steps:
1. [Export your data from Supabase](#export-your-supabase-data)
2. [Prepare your data for Meilisearch](#prepare-your-data)
3. [Import your data into Meilisearch](#import-your-data-into-meilisearch)
4. [Configure your Meilisearch index settings (optional)](#configure-your-index-settings)
To help with the transition, this guide also includes a comparison of [settings and parameters](#settings-and-parameters-comparison), [query types](#query-comparison), and practical advice for [keeping data in sync](#keeping-data-in-sync).
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](/docs/resources/self_hosting/getting_started/quick_start).
This guide includes examples in JavaScript, Python, and Ruby. The packages used:
* **JavaScript**: [`@supabase/supabase-js`](https://www.npmjs.com/package/@supabase/supabase-js), [`meilisearch`](https://www.npmjs.com/package/meilisearch) (compatible with Meilisearch v1.0+)
* **Python**: [`supabase`](https://pypi.org/project/supabase/), [`meilisearch`](https://pypi.org/project/meilisearch/)
* **Ruby**: [`pg`](https://rubygems.org/gems/pg), [`meilisearch`](https://rubygems.org/gems/meilisearch) (there is no official Supabase Ruby client, connect directly to PostgreSQL using your Supabase connection string)
## Export your Supabase data
### Initialize project
```bash JavaScript theme={null}
mkdir supabase-meilisearch-migration
cd supabase-meilisearch-migration
npm init -y
touch script.js
```
```bash Python theme={null}
mkdir supabase-meilisearch-migration
cd supabase-meilisearch-migration
touch script.py
```
```bash Ruby theme={null}
mkdir supabase-meilisearch-migration
cd supabase-meilisearch-migration
touch script.rb
```
### Install dependencies
```bash JavaScript theme={null}
npm install -s @supabase/supabase-js meilisearch
```
```bash Python theme={null}
pip install supabase meilisearch
```
```bash Ruby theme={null}
gem install pg meilisearch
```
### Create Supabase client
You need your Supabase **project URL** and **service role key** (not the anon key, since the service role key bypasses Row Level Security and can read all rows). For Ruby, use the **direct database connection string** from your Supabase project settings.
```javascript JavaScript theme={null}
const { createClient } = require("@supabase/supabase-js");
const supabase = createClient(
"SUPABASE_URL", // e.g. https://xxxxx.supabase.co
"SUPABASE_SERVICE_KEY" // service_role key from Settings > API
);
```
```python Python theme={null}
from supabase import create_client
supabase = create_client(
"SUPABASE_URL", # e.g. https://xxxxx.supabase.co
"SUPABASE_SERVICE_KEY" # service_role key from Settings > API
)
```
```ruby Ruby theme={null}
require 'pg'
# Use the direct connection string from Supabase > Settings > Database
pg_conn = PG.connect(
host: 'db.xxxxx.supabase.co',
port: 5432,
dbname: 'postgres',
user: 'postgres',
password: 'SUPABASE_DB_PASSWORD'
)
```
Replace the placeholder values with your Supabase project credentials. You can find these in your Supabase dashboard under **Settings > API** (for URL and keys) or **Settings > Database** (for the direct connection string used by Ruby).
### Fetch data from Supabase
Use range-based pagination to retrieve all rows. The Supabase client's `.range(from, to)` method returns up to 1,000 rows per request by default.
```javascript JavaScript theme={null}
const TABLE_NAME = "YOUR_TABLE_NAME";
const BATCH_SIZE = 1000;
async function fetchAllRows() {
const records = [];
let from = 0;
while (true) {
const { data, error } = await supabase
.from(TABLE_NAME)
.select("*")
.range(from, from + BATCH_SIZE - 1);
if (error) throw error;
if (!data || data.length === 0) break;
records.push(...data);
from += data.length;
// If we got fewer rows than the batch size, we've reached the end
if (data.length < BATCH_SIZE) break;
}
return records;
}
```
```python Python theme={null}
TABLE_NAME = "YOUR_TABLE_NAME"
BATCH_SIZE = 1000
def fetch_all_rows():
records = []
start = 0
while True:
response = (
supabase.table(TABLE_NAME)
.select("*")
.range(start, start + BATCH_SIZE - 1)
.execute()
)
rows = response.data
if not rows:
break
records.extend(rows)
start += len(rows)
# If we got fewer rows than the batch size, we've reached the end
if len(rows) < BATCH_SIZE:
break
return records
```
```ruby Ruby theme={null}
TABLE_NAME = 'YOUR_TABLE_NAME'
BATCH_SIZE = 10_000
def fetch_all_rows(pg_conn)
records = []
offset = 0
loop do
result = pg_conn.exec_params(
"SELECT * FROM #{TABLE_NAME} ORDER BY id LIMIT $1 OFFSET $2",
[BATCH_SIZE, offset]
)
break if result.ntuples.zero?
result.each { |row| records << row }
offset += result.ntuples
end
records
end
```
Replace `YOUR_TABLE_NAME` with the name of the table you want to migrate. If your table does not have an `id` column, replace it with your primary key column name in the Ruby example.
For very large tables (millions of rows), consider exporting data using the Supabase CLI (`supabase db dump`) or connecting directly to PostgreSQL to use the `COPY` command.
## Prepare your data
Supabase rows returned by the JavaScript and Python clients are already JSON objects, so they map naturally to Meilisearch documents. You mainly need to ensure a primary key field exists, remove any derived `tsvector` columns (they cannot be serialized), and handle any `embedding` vector columns from Supabase Vector.
```javascript JavaScript theme={null}
function prepareDocuments(rows) {
return rows.map((row) => {
const doc = { ...row };
// Ensure the primary key is a string named "id"
if (doc.id === undefined && doc.your_pk_column !== undefined) {
doc.id = String(doc.your_pk_column);
} else {
doc.id = String(doc.id);
}
// Remove tsvector columns (they are derived and not needed)
delete doc.fts; // common Supabase convention for tsvector columns
// Remove embedding columns (Meilisearch auto-embedder replaces these)
delete doc.embedding;
return doc;
});
}
```
```python Python theme={null}
def prepare_documents(rows):
documents = []
for row in rows:
doc = {**row}
# Ensure the primary key is a string named "id"
if "id" not in doc and "your_pk_column" in doc:
doc["id"] = str(doc["your_pk_column"])
else:
doc["id"] = str(doc["id"])
# Remove tsvector columns (they are derived and not needed)
doc.pop("fts", None) # common Supabase convention for tsvector columns
# Remove embedding columns (Meilisearch auto-embedder replaces these)
doc.pop("embedding", None)
documents.append(doc)
return documents
```
```ruby Ruby theme={null}
require 'json'
def prepare_documents(rows)
rows.map do |row|
doc = row.dup
# Ensure the primary key is a string named "id"
if doc['id'].nil? && doc['your_pk_column']
doc['id'] = doc['your_pk_column'].to_s
else
doc['id'] = doc['id'].to_s
end
# Remove tsvector columns (they are derived and not needed)
doc.delete('fts') # common Supabase convention for tsvector columns
# Remove embedding columns (Meilisearch auto-embedder replaces these)
doc.delete('embedding')
doc
end
end
```
If your primary key column is not called `id`, you can either rename it in the preparation step (as shown above) or tell Meilisearch which field to use as the primary key when creating the index. Replace `your_pk_column` with the actual column name.
### Handle PostGIS geo data
If your Supabase table uses PostGIS geography or geometry columns, convert them to Meilisearch's `_geo` format. You need to extract coordinates from PostGIS. For JavaScript and Python, add a database function or use the direct PostgreSQL connection. For Ruby, modify the SQL query:
```javascript JavaScript theme={null}
// If your table has a PostGIS "location" column, create a Supabase database
// function that returns lat/lng, or query via the PostgreSQL connection directly.
// Alternatively, if you store lat/lng as separate columns:
function convertGeoFields(doc) {
if (doc.lat !== undefined && doc.lng !== undefined) {
doc._geo = {
lat: parseFloat(doc.lat),
lng: parseFloat(doc.lng),
};
delete doc.lat;
delete doc.lng;
}
delete doc.location;
return doc;
}
```
```python Python theme={null}
# If your table has a PostGIS "location" column, create a Supabase database
# function that returns lat/lng, or query via the PostgreSQL connection directly.
# Alternatively, if you store lat/lng as separate columns:
def convert_geo_fields(doc):
if "lat" in doc and "lng" in doc:
doc["_geo"] = {
"lat": float(doc["lat"]),
"lng": float(doc["lng"]),
}
del doc["lat"]
del doc["lng"]
doc.pop("location", None)
return doc
```
```ruby Ruby theme={null}
# When querying, extract lat/lng from PostGIS:
# SELECT *, ST_Y(location::geometry) AS lat, ST_X(location::geometry) AS lng FROM your_table
def convert_geo_fields(doc)
if doc['lat'] && doc['lng']
doc['_geo'] = {
'lat' => doc['lat'].to_f,
'lng' => doc['lng'].to_f
}
doc.delete('lat')
doc.delete('lng')
end
doc.delete('location')
doc
end
```
## 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](/docs/resources/self_hosting/security/basic_security).
```javascript JavaScript theme={null}
const { Meilisearch } = require("meilisearch");
const meiliClient = new Meilisearch({
host: "MEILI_HOST",
apiKey: "MEILI_API_KEY",
});
const meiliIndex = meiliClient.index("MEILI_INDEX_NAME");
```
```python Python theme={null}
import meilisearch
meili_client = meilisearch.Client("MEILI_HOST", "MEILI_API_KEY")
meili_index = meili_client.index("MEILI_INDEX_NAME")
```
```ruby Ruby theme={null}
require 'meilisearch'
meili_client = MeiliSearch::Client.new('MEILI_HOST', 'MEILI_API_KEY')
meili_index = meili_client.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.
```javascript JavaScript theme={null}
const UPLOAD_BATCH_SIZE = 100000;
await meiliIndex.addDocumentsInBatches(documents, UPLOAD_BATCH_SIZE);
```
```python Python theme={null}
UPLOAD_BATCH_SIZE = 100000
meili_index.add_documents_in_batches(documents, batch_size=UPLOAD_BATCH_SIZE)
```
```ruby Ruby theme={null}
UPLOAD_BATCH_SIZE = 100_000
meili_index.add_documents_in_batches(documents, UPLOAD_BATCH_SIZE)
```
When you're ready, run the script:
```bash JavaScript theme={null}
node script.js
```
```bash Python theme={null}
python script.py
```
```bash Ruby theme={null}
ruby script.rb
```
### Finished script
```javascript JavaScript theme={null}
const { createClient } = require("@supabase/supabase-js");
const { Meilisearch } = require("meilisearch");
const TABLE_NAME = "YOUR_TABLE_NAME";
const FETCH_BATCH_SIZE = 1000;
const UPLOAD_BATCH_SIZE = 100000;
(async () => {
// Connect to Supabase
const supabase = createClient(
"SUPABASE_URL",
"SUPABASE_SERVICE_KEY"
);
// Fetch all rows using range-based pagination
const records = [];
let from = 0;
while (true) {
const { data, error } = await supabase
.from(TABLE_NAME)
.select("*")
.range(from, from + FETCH_BATCH_SIZE - 1);
if (error) throw error;
if (!data || data.length === 0) break;
records.push(...data);
from += data.length;
if (data.length < FETCH_BATCH_SIZE) break;
}
// Prepare documents for Meilisearch
const documents = records.map((row) => {
const doc = { ...row };
doc.id = String(doc.id);
// Remove derived columns that Meilisearch doesn't need
delete doc.fts;
delete doc.embedding;
return doc;
});
console.log(`Fetched ${documents.length} rows from Supabase`);
// 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");
})();
```
```python Python theme={null}
from supabase import create_client
import meilisearch
TABLE_NAME = "YOUR_TABLE_NAME"
FETCH_BATCH_SIZE = 1000
UPLOAD_BATCH_SIZE = 100000
# Connect to Supabase
supabase = create_client("SUPABASE_URL", "SUPABASE_SERVICE_KEY")
# Fetch all rows using range-based pagination
records = []
start = 0
while True:
response = (
supabase.table(TABLE_NAME)
.select("*")
.range(start, start + FETCH_BATCH_SIZE - 1)
.execute()
)
rows = response.data
if not rows:
break
records.extend(rows)
start += len(rows)
if len(rows) < FETCH_BATCH_SIZE:
break
# Prepare documents for Meilisearch
documents = []
for row in records:
doc = {**row}
doc["id"] = str(doc["id"])
# Remove derived columns that Meilisearch doesn't need
doc.pop("fts", None)
doc.pop("embedding", None)
documents.append(doc)
print(f"Fetched {len(documents)} rows from Supabase")
# Upload to Meilisearch
meili_client = meilisearch.Client("MEILI_HOST", "MEILI_API_KEY")
meili_index = meili_client.index("MEILI_INDEX_NAME")
meili_index.add_documents_in_batches(documents, batch_size=UPLOAD_BATCH_SIZE)
print("Migration complete")
```
```ruby Ruby theme={null}
require 'pg'
require 'meilisearch'
TABLE_NAME = 'YOUR_TABLE_NAME'
FETCH_BATCH_SIZE = 10_000
UPLOAD_BATCH_SIZE = 100_000
# Connect directly to Supabase PostgreSQL
pg_conn = PG.connect(
host: 'db.xxxxx.supabase.co',
port: 5432,
dbname: 'postgres',
user: 'postgres',
password: 'SUPABASE_DB_PASSWORD'
)
# Fetch all rows
records = []
offset = 0
loop do
result = pg_conn.exec_params(
"SELECT * FROM #{TABLE_NAME} ORDER BY id LIMIT $1 OFFSET $2",
[FETCH_BATCH_SIZE, offset]
)
break if result.ntuples.zero?
result.each { |row| records << row }
offset += result.ntuples
end
pg_conn.close
# Prepare documents for Meilisearch
documents = records.map do |row|
doc = row.dup
doc['id'] = doc['id'].to_s
# Remove derived columns that Meilisearch doesn't need
doc.delete('fts')
doc.delete('embedding')
doc
end
puts "Fetched #{documents.length} rows from Supabase"
# Upload to Meilisearch
meili_client = MeiliSearch::Client.new('MEILI_HOST', 'MEILI_API_KEY')
meili_index = meili_client.index('MEILI_INDEX_NAME')
meili_index.add_documents_in_batches(documents, UPLOAD_BATCH_SIZE)
puts 'Migration complete'
```
## Configure your index settings
Meilisearch's default settings deliver relevant, typo-tolerant search out of the box. Unlike Supabase, where `.textSearch()` is syntactic sugar over PostgreSQL's `to_tsquery()` and requires `tsvector` columns and GIN indexes, Meilisearch indexes all fields automatically and handles tokenization, stemming, and typo tolerance without any configuration.
To customize your index settings, see [configuring index settings](/docs/resources/internals/indexes#index-settings). To understand the differences between Supabase search and Meilisearch, read on.
### Key conceptual differences
**Supabase full-text search** is a convenience layer over PostgreSQL's built-in search. The `.textSearch()` client method translates to `to_tsquery()` under the hood. You still need `tsvector` columns, GIN indexes, and language configurations. There is no typo tolerance, no prefix search by default, and relevancy ranking requires manual `ts_rank()` calls.
**Supabase Vector** uses the pgvector extension to store and query vector embeddings. You must generate embeddings in your application code or Supabase Edge Functions, store them in a `vector` column, and write RPC functions like `match_documents()` to perform similarity search. This adds significant complexity to your stack.
**Meilisearch** is a dedicated search engine. You send documents and search queries, and everything else is automatic. Tokenization, stemming, typo tolerance, prefix search, and ranking all work out of the box. Because Meilisearch runs as a separate service, search queries never impact your Supabase database performance.
### Configure embedders for hybrid search
If you currently use Supabase Vector for semantic similarity search, you can replace the entire pipeline (embedding generation in Edge Functions, vector columns, RPC functions, pgvector indexes) with Meilisearch's built-in hybrid search. Configure an [embedder](/docs/capabilities/hybrid_search/getting_started) and Meilisearch handles all vectorization automatically, both at indexing time and at search time.
```bash theme={null}
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. With this single configuration, you can remove:
* Supabase Edge Functions that generate embeddings
* The `embedding` vector column from your table
* The `match_documents()` RPC function
* Any pgvector indexes (ivfflat or hnsw)
* Client-side embedding generation code
For more options including HuggingFace models, Ollama, and custom REST endpoints, see [configuring embedders](/docs/capabilities/hybrid_search/getting_started).
If you already have embeddings stored in a pgvector `vector` column and prefer not to re-embed, export them from Supabase and include them in the `_vectors` field of each document. Then configure a `userProvided` embedder:
```bash theme={null}
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 pgvector embeddings. With this approach, you remain responsible for computing and providing vectors when adding or updating documents, and for computing query vectors client-side when searching.
### Configure filterable and sortable attributes
In Supabase, any column can be used with `.eq()`, `.gt()`, `.lt()`, and `.order()`. In Meilisearch, you must declare which fields are [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) and [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes):
```bash theme={null}
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", "status", "price", "_geo"],
"sortableAttributes": ["price", "created_at", "_geo"]
}'
```
### What you gain
Migrating your search layer from Supabase to Meilisearch gives you several features that work out of the box:
* **Typo tolerance**: Supabase's `.textSearch()` inherits PostgreSQL's zero typo tolerance. A single typo returns zero results. Meilisearch handles typos automatically, so "reciepe" finds "recipe"
* **Prefix search**: Users see results as they type, without needing trigram indexes or `LIKE` queries
* **Instant results**: Sub-50ms search responses regardless of dataset complexity, with no GIN index tuning
* **Highlighting** of matching terms in results, without manually calling `ts_headline()` via RPC
* **Faceted search** with value distributions for building filter UIs, no `GROUP BY` queries or RPC functions needed
* **Hybrid search** combining keyword relevancy and semantic similarity in a single query, replacing separate `.textSearch()` and `match_documents()` pipelines
* **No search infrastructure in your database**: Remove `tsvector` columns, GIN indexes, embedding columns, pgvector indexes, RPC functions, and Edge Functions for embedding generation. Your Supabase database handles what it does best (transactions and relational data), while Meilisearch handles search
## Settings and parameters comparison
### Supabase client methods
| Supabase client | Meilisearch | Notes |
| :-------------------------------------- | :------------------------------------------- | :------------------------------------------------------------------------------------- |
| `.textSearch(column, query)` | `q` search param | Just send the user's text, no tsquery construction needed |
| `.eq(column, value)` | `filter` with `=` | Requires [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) |
| `.gt()` / `.gte()` / `.lt()` / `.lte()` | `filter` with `>`, `>=`, `<`, `<=` | Requires [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) |
| `.in(column, values)` | `filter` with `IN [v1, v2]` | Requires [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) |
| `.order(column, { ascending })` | `sort` search param | Requires [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes) |
| `.range(from, to)` | `offset` / `limit` or `page` / `hitsPerPage` | Search params |
| `.select(columns)` | `attributesToRetrieve` | Search param |
| `.limit(count)` | `limit` | Search param |
| No equivalent | `attributesToHighlight` | Highlight matching terms in results |
| No equivalent | `facets` | Get value distributions for fields |
| No equivalent | `hybrid` | Combined keyword + semantic search |
### Supabase Vector (pgvector)
| Supabase Vector | Meilisearch | Notes |
| :------------------------------------- | :------------------------------------- | :----------------------------------------------------- |
| `match_documents()` RPC function | `hybrid` + auto-embedder | No RPC functions needed, just send a text query |
| pgvector `<=>` cosine operator | Automatic via configured embedder | Distance metric handled internally |
| `embedding` vector column | Not needed with auto-embedder | Meilisearch generates and stores vectors automatically |
| Embedding generation in Edge Functions | Automatic via configured embedder | Remove all embedding generation code |
| `vecs` Python library | `meilisearch` Python SDK with `hybrid` | Single SDK for all search types |
| hnsw / ivfflat index on vector column | Automatic (DiskANN-based) | No index type selection needed |
| `match_count` parameter | `limit` search param | Search param |
### PostgreSQL concepts (underlying Supabase)
| PostgreSQL concept | Meilisearch | Notes |
| :-------------------------------------------- | :---------------------------------------------------------------------------- | :----------------------------------------------------- |
| `to_tsvector(config, text)` | Automatic tokenization | No text processing functions needed |
| `to_tsquery()` / `plainto_tsquery()` | `q` search param | Just send the user's text |
| `ts_rank()` / `ts_rank_cd()` | Built-in [ranking rules](/docs/reference/api/settings/update-ranking-rules) | Relevancy ranking is automatic and configurable |
| `tsvector` column + GIN index | Automatic | Meilisearch indexes all fields automatically |
| Language configurations (`english`, `french`) | [`localizedAttributes`](/docs/reference/api/settings/update-localizedattributes) | Assign languages to specific fields |
| `setweight()` (A, B, C, D) | [`searchableAttributes`](/docs/reference/api/settings/update-searchableattributes) | Ordered list, fields listed first have higher priority |
| `tsvector` update triggers | Automatic | Meilisearch re-indexes on every document update |
| No typo tolerance | Automatic [typo tolerance](/docs/reference/api/settings/update-typotolerance) | Configurable per index |
## Query comparison
This section shows how common Supabase search operations translate to Meilisearch. All Supabase examples use the JavaScript client syntax (the most widely used). Meilisearch examples are shown as JSON POST requests.
### Full-text search
**Supabase:**
```javascript theme={null}
const { data } = await supabase
.from('products')
.select()
.textSearch('name', 'running shoes')
.limit(20)
```
**Meilisearch:**
```json theme={null}
POST /indexes/products/search
{
"q": "running shoes",
"limit": 20
}
```
No `tsvector` columns, no `@@` operator, no `ts_rank()` function. Just send the text. Meilisearch also handles typos, so searching for "runnign shoes" still returns the right results.
### Filtered search
**Supabase:**
```javascript theme={null}
const { data } = await supabase
.from('products')
.select()
.textSearch('name', 'laptop')
.eq('category', 'electronics')
.gte('price', 500)
.lte('price', 1500)
```
**Meilisearch:**
```json theme={null}
POST /indexes/products/search
{
"q": "laptop",
"filter": "category = electronics AND price >= 500 AND price <= 1500"
}
```
Attributes used in `filter` must first be added to [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes).
### Sorting
**Supabase:**
```javascript theme={null}
const { data } = await supabase
.from('products')
.select()
.textSearch('name', 'shoes')
.order('price', { ascending: true })
```
**Meilisearch:**
```json theme={null}
POST /indexes/products/search
{
"q": "shoes",
"sort": ["price:asc"]
}
```
Attributes used in `sort` must first be added to [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes).
### Vector / semantic search
**Supabase (requires Edge Function for embedding + RPC function):**
```javascript theme={null}
// First, generate the embedding (typically in an Edge Function)
const embeddingResponse = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: 'comfortable running shoes',
})
const queryEmbedding = embeddingResponse.data[0].embedding
// Then call the RPC function
const { data } = await supabase.rpc('match_documents', {
query_embedding: queryEmbedding,
match_count: 10,
})
```
**Meilisearch:**
```json theme={null}
POST /indexes/products/search
{
"q": "comfortable running shoes",
"hybrid": {
"semanticRatio": 1.0,
"embedder": "default"
},
"limit": 10
}
```
With an auto-embedder configured, Meilisearch embeds the `q` text for you. No client-side embedding generation, no Edge Functions, no RPC functions. Setting `semanticRatio` to `1.0` performs pure semantic search. Use a value like `0.5` to combine keyword and semantic results in a single hybrid query.
### Faceted search
**Supabase (requires a custom RPC function):**
```javascript theme={null}
// Must create a PostgreSQL function first:
// CREATE FUNCTION get_category_counts(search_query text)
// RETURNS TABLE(category text, count bigint) AS $$
// SELECT category, COUNT(*)
// FROM products
// WHERE to_tsvector('english', name) @@ plainto_tsquery('english', search_query)
// GROUP BY category ORDER BY count DESC
// $$ LANGUAGE sql;
const { data } = await supabase.rpc('get_category_counts', {
search_query: 'shoes',
})
```
**Meilisearch:**
```json theme={null}
POST /indexes/products/search
{
"q": "shoes",
"facets": ["category", "brand", "color"]
}
```
Meilisearch returns search results and value distributions for all requested facets in a single response, no custom RPC functions or `GROUP BY` queries needed.
### Geo search
**Supabase (requires PostGIS + RPC function):**
```javascript theme={null}
// Must create a PostgreSQL function using PostGIS:
// CREATE FUNCTION nearby_restaurants(lat float, lng float, radius_m float)
// RETURNS SETOF restaurants AS $$
// SELECT * FROM restaurants
// WHERE ST_DWithin(location, ST_MakePoint(lng, lat)::geography, radius_m)
// ORDER BY ST_Distance(location, ST_MakePoint(lng, lat)::geography)
// $$ LANGUAGE sql;
const { data } = await supabase.rpc('nearby_restaurants', {
lat: 48.8566,
lng: 2.3522,
radius_m: 5000,
})
```
**Meilisearch:**
```json theme={null}
POST /indexes/restaurants/search
{
"filter": "_geoRadius(48.8566, 2.3522, 5000)",
"sort": ["_geoPoint(48.8566, 2.3522):asc"]
}
```
The `_geo` attribute must be added to both [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes) and [`sortableAttributes`](/docs/reference/api/settings/update-sortableattributes).
## Keeping data in sync
Since Supabase remains your source of truth, you need a strategy to keep Meilisearch in sync when data changes. Supabase offers several built-in mechanisms that make this straightforward.
### Database Webhooks
Supabase Database Webhooks trigger an HTTP request on INSERT, UPDATE, or DELETE events. Point them at a serverless function that updates Meilisearch:
1. Go to **Supabase Dashboard > Database > Webhooks**
2. Create a webhook for your table, selecting the events you want to track
3. Set the URL to a serverless function (Supabase Edge Function, Vercel, etc.) that forwards the change to Meilisearch
### Supabase Edge Functions
Create an Edge Function that receives webhook payloads and syncs changes to Meilisearch:
```typescript theme={null}
// supabase/functions/sync-to-meilisearch/index.ts
import { Meilisearch } from "npm:meilisearch";
const meili = new Meilisearch({
host: Deno.env.get("MEILI_HOST")!,
apiKey: Deno.env.get("MEILI_API_KEY")!,
});
Deno.serve(async (req) => {
const payload = await req.json();
const { type, record, old_record } = payload;
const index = meili.index("your_index");
if (type === "INSERT" || type === "UPDATE") {
await index.addDocuments([{ ...record, id: String(record.id) }]);
} else if (type === "DELETE") {
await index.deleteDocument(String(old_record.id));
}
return new Response("ok");
});
```
### Supabase Realtime
Subscribe to database changes from your application and sync them as they happen:
```javascript theme={null}
supabase
.channel('meilisearch-sync')
.on('postgres_changes', { event: '*', schema: 'public', table: 'products' },
async (payload) => {
const index = meiliClient.index('products')
if (payload.eventType === 'DELETE') {
await index.deleteDocument(String(payload.old.id))
} else {
await index.addDocuments([{ ...payload.new, id: String(payload.new.id) }])
}
}
)
.subscribe()
```
### Periodic batch sync
Run a scheduled job that queries Supabase for recently modified rows:
```javascript theme={null}
const since = new Date(Date.now() - 5 * 60 * 1000).toISOString() // last 5 minutes
const { data } = await supabase
.from('products')
.select('*')
.gte('updated_at', since)
if (data && data.length > 0) {
await meiliIndex.addDocuments(data.map(row => ({
...row,
id: String(row.id),
})))
}
```
For most applications, Database Webhooks with an Edge Function provide the best balance of simplicity and freshness. Meilisearch's `addDocuments` method is an upsert: sending an existing document with the same primary key updates it automatically.
## Front-end components
Supabase does not include front-end search components. Meilisearch is compatible with Algolia's [InstantSearch](https://github.com/algolia/instantsearch.js) libraries through [Instant Meilisearch](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/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](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/instant-meilisearch#-api-resources) in the GitHub project's README.
# Update to the latest Meilisearch version
Source: https://www.meilisearch.com/docs/resources/migration/updating
Learn how to migrate to the latest Meilisearch release.
Meilisearch databases are only compatible with the version of Meilisearch used to create them. The following guide will walk you through upgrading an existing database from an older version of Meilisearch to the most recent one, either with the `--upgrade-db` flag or with a [dump](/docs/resources/self_hosting/data_backup/dumps).
If you're updating your Meilisearch instance on cloud platforms like DigitalOcean or AWS, ensure that you can connect to your cloud instance via SSH. Depending on the user you are connecting with (root, admin, etc.), you may need to prefix some commands with `sudo`.
If migrating to the latest version of Meilisearch will cause you to skip multiple versions, this may require changes to your codebase. [Refer to our version-specific update warnings for more details](#version-specific-warnings).
## Updating Meilisearch Cloud
Log into your Meilisearch Cloud account and navigate to the project you want to update.
Click on the project you want to update. Look for the "General settings" section at the top of the page.
Whenever a new version of Meilisearch is available, you will see an update button next to the "Meilisearch version" field.
To update to the latest Meilisearch release, click the "Update to v.X.Y.Z" button.
This will open a pop-up with more information about the update process. Read it, then click on "Update". The "Status" of your project will change from "running" to "updating".
Once the project has been successfully updated, you will receive an email confirming the update and "Status" will change back to "running".
## Updating a self-hosted Meilisearch instance
To update a self-hosted instance, create a snapshot of your data, install the new binary, and relaunch Meilisearch with the `--upgrade-db` flag. Meilisearch then upgrades your database on startup.
In some cases, Meilisearch cannot upgrade your database this way and returns an error at launch. If that happens, [update your instance using a dump](#using-a-dump) instead.
### Updating with the `--upgrade-db` flag
The `--upgrade-db` flag is available since Meilisearch v1.51. If you are upgrading to an older version, use `--experimental-dumpless-upgrade` instead.
#### Step 1: Make a backup
Database upgrades are not atomic. In rare occasions, the process may partially fail and result in a corrupted database. To prevent data loss, create a snapshot of your instance:
```bash cURL theme={null}
curl \
-X POST 'MEILISEARCH_URL/snapshots'
```
```javascript JS theme={null}
client.createSnapshot()
```
```python Python theme={null}
client.create_snapshot()
```
```php PHP theme={null}
$client->createSnapshot();
```
```java Java theme={null}
client.createSnapshot();
```
```ruby Ruby theme={null}
client.create_snapshot
```
```go Go theme={null}
client.CreateSnapshot()
```
```csharp C# theme={null}
await client.CreateSnapshotAsync();
```
```rust Rust theme={null}
client
.create_snapshot()
.await
.unwrap();
```
```swift Swift theme={null}
let task = try await self.client.createSnapshot()
```
Meilisearch will respond with a partial task object. Use its `taskUid` to monitor the snapshot creation status. Once the task is completed, proceed to the next step.
#### Step 2: Stop the Meilisearch instance
Next, stop your Meilisearch instance.
If you're running Meilisearch locally, stop the program by pressing `Ctrl + c`.
If you're running Meilisearch as a `systemctl` service, connect via SSH to your cloud instance and execute the following command to stop Meilisearch:
```bash theme={null}
systemctl stop meilisearch
```
You may need to prefix the above command with `sudo` if you are not connected as root.
#### Step 3: Install the new Meilisearch binary
Install the latest version of Meilisearch using:
```bash theme={null}
curl -L https://install.meilisearch.com | sh
```
```sh theme={null}
# replace MEILISEARCH_VERSION with the version of your choice. Use the format: `vX.X.X`
curl "https://github.com/meilisearch/meilisearch/releases/download/MEILISEARCH_VERSION/meilisearch-linux-amd64" --output meilisearch --location --show-error
```
Give execute permission to the Meilisearch binary:
```
chmod +x meilisearch
```
For **cloud platforms**, move the new Meilisearch binary to the `/usr/bin` directory:
```
mv meilisearch /usr/bin/meilisearch
```
#### Step 4: Relaunch Meilisearch
Relaunch Meilisearch with the `--upgrade-db` flag:
```bash theme={null}
./meilisearch --upgrade-db
```
```sh theme={null}
meilisearch --upgrade-db
```
Meilisearch should launch normally and immediately create a new `UpgradeDatabase` task. This task is processed immediately and cannot be canceled. You may follow its progress by using the `GET /tasks?types=UpgradeDatabase` endpoint to obtain its `taskUid`, then querying `GET /tasks/TASK_UID`.
While the task is processing, you may continue making search queries. You may also enqueue new tasks. Meilisearch will only process new tasks once `UpgradeDatabase` is completed.
#### If Meilisearch fails to launch with an error
Meilisearch cannot upgrade databases created with versions older than v1.12. In that case, it fails to launch and returns the following error:
```
Database version X.Y.Z is too old to be upgraded via `--upgrade-db`. Please generate a dump using the vX.Y.Z and import it in the vA.B.C
```
If you see this error, reinstall the Meilisearch version your database was created with, relaunch your instance, then [update it using a dump](#using-a-dump).
#### Rolling back an update
If the upgrade is taking too long, or if after the upgrade is completed its task status is set to `failed`, you can cancel the upgrade task.
Cancelling the update task automatically rolls back your database to its state before the upgrade began.
After launching Meilisearch with the `--upgrade-db` flag:
1. Cancel the `upgradeDatabase` task
2. If you cancelled the update before it failed, skip to the next step. If the update failed, relaunch Meilisearch using the binary of the version you were upgrading to
3. Wait for Meilisearch to process your cancellation request
4. Replace the new binary with the binary of the previous version
5. Relaunch Meilisearch
If you are upgrading Meilisearch to \<= v1.14, you must instead [restart your instance from the snapshot](/docs/resources/self_hosting/data_backup/snapshots#starting-from-a-snapshot) you generated during step 1. You may then retry the upgrade, or upgrade using a dump. You are also welcome to open an issue on the [Meilisearch repository](https://github.com/meilisearch/meilisearch).
### Using a dump
Use this method when Meilisearch cannot upgrade your database with the `--upgrade-db` flag.
#### Step 1: Export data
##### Verify your database version
First, verify the version of Meilisearch that's compatible with your database using the get version endpoint:
```bash cURL theme={null}
curl \
-X GET 'http:///version' \
-H 'Authorization: Bearer API_KEY'
```
The response should look something like this:
```json theme={null}
{
"commitSha": "stringOfLettersAndNumbers",
"commitDate": "YYYY-MM-DDTimestamp",
"pkgVersion": "x.y.z"
}
```
Proceed to [creating the dump](/docs/reference/api/management/create-dump).
##### Create the dump
Before creating your dump, make sure that your [dump directory](/docs/resources/self_hosting/configuration/reference#dump-directory) is somewhere accessible. By default, dumps are created in a folder called `dumps` at the root of your Meilisearch directory.
**Cloud platforms** like DigitalOcean and AWS are configured to store dumps in the `/var/opt/meilisearch/dumps` directory.
If you're unsure where your Meilisearch directory is located, try this:
```bash theme={null}
which meilisearch
```
It should return something like this:
```bash theme={null}
/absolute/path/to/your/meilisearch/directory
```
```bash theme={null}
where meilisearch
```
It should return something like this:
```bash theme={null}
/absolute/path/to/your/meilisearch/directory
```
```bash theme={null}
(Get-Command meilisearch).Path
```
It should return something like this:
```bash theme={null}
/absolute/path/to/your/meilisearch/directory
```
You can then create a dump of your database using the [create a dump endpoint](/docs/reference/api/management/create-dump):
```bash cURL theme={null}
curl \
-X POST 'http:///dumps' \
-H 'Authorization: Bearer API_KEY'
```
The server should return a response that looks like this:
```json theme={null}
{
"taskUid": 1,
"indexUid": null,
"status": "enqueued",
"type": "dumpCreation",
"enqueuedAt": "2022-06-21T16:10:29.217688Z"
}
```
Use the `taskUid` to [track the status](/docs/reference/api/tasks/get-task) of your dump. Keep in mind that the process can take some time to complete.
Once the `dumpCreation` task shows `"status": "succeeded"`, you're ready to move on.
#### Step 2: Prepare for migration
##### Stop the Meilisearch instance
Stop your Meilisearch instance.
If you're running Meilisearch locally, you can stop the program with `Ctrl + c`.
If you're running Meilisearch as a `systemctl` service, connect via SSH to your cloud instance and execute the following command to stop Meilisearch:
```bash theme={null}
systemctl stop meilisearch
```
You may need to prefix the above command with `sudo` if you are not connected as root.
##### Create a backup
Instead of deleting `data.ms`, we suggest creating a backup in case something goes wrong. `data.ms` should be at the root of the Meilisearch binary unless you chose [another location](/docs/resources/self_hosting/configuration/reference#database-path).
On **cloud platforms**, you will find the `data.ms` folder at `/var/lib/meilisearch/data.ms`.
Move the binary of the current Meilisearch installation and database to the `/tmp` folder:
```
mv /path/to/your/meilisearch/directory/meilisearch/data.ms /tmp/
mv /path/to/your/meilisearch/directory/meilisearch /tmp/
```
```
mv /usr/bin/meilisearch /tmp/
mv /var/lib/meilisearch/data.ms /tmp/
```
##### Install the desired version of Meilisearch
Install the latest version of Meilisearch using:
```bash theme={null}
curl -L https://install.meilisearch.com | sh
```
```sh theme={null}
# replace {meilisearch_version} with the version of your choice. Use the format: `vX.X.X`
curl "https://github.com/meilisearch/meilisearch/releases/download/{meilisearch_version}/meilisearch-linux-amd64" --output meilisearch --location --show-error
```
Give execute permission to the Meilisearch binary:
```
chmod +x meilisearch
```
For **cloud platforms**, move the new Meilisearch binary to the `/usr/bin` directory:
```
mv meilisearch /usr/bin/meilisearch
```
#### Step 3: Import data
##### Launch Meilisearch and import the dump
Execute the command below to import the dump at launch:
```bash theme={null}
# replace {dump_uid.dump} with the name of your dump file
./meilisearch --import-dump dumps/{dump_uid.dump} --master-key="MASTER_KEY"
# Or, if you chose another location for data files and dumps before the update, also add the same parameters
./meilisearch --import-dump dumps/{dump_uid.dump} --master-key="MASTER_KEY" --db-path PATH_TO_DB_DIR/data.ms --dump-dir PATH_TO_DUMP_DIR/dumps
```
```sh theme={null}
# replace {dump_uid.dump} with the name of your dump file
meilisearch --db-path /var/lib/meilisearch/data.ms --import-dump "/var/opt/meilisearch/dumps/{dump_uid.dump}"
```
Importing a dump requires indexing all the documents it contains. Depending on the size of your dataset, this process can take a long time and cause a spike in memory usage.
##### Restart Meilisearch as a service
If you're running a **cloud instance**, press `Ctrl`+`C` to stop Meilisearch once your dump has been correctly imported. Next, execute the following command to run the script to configure Meilisearch and restart it as a service:
```
meilisearch-setup
```
If required, set `displayedAttributes` back to its previous value using the [update displayed attributes endpoint](/docs/reference/api/settings/update-displayedattributes).
### Conclusion
Now that your updated Meilisearch instance is up and running, verify that the dump import was successful and no data was lost.
If everything looks good, then congratulations! You successfully migrated your database to the latest version of Meilisearch. Be sure to check out the [changelogs](https://github.com/meilisearch/MeiliSearch/releases).
If something went wrong, you can always roll back to the previous version. Feel free to [reach out for help](https://discord.meilisearch.com) if the problem continues. If you successfully migrated your database but are having problems with your codebase, be sure to check out our [version-specific warnings](#version-specific-warnings).
#### Delete backup files or rollback (*optional*)
Delete the Meilisearch binary and `data.ms` folder created by the previous steps. Next, move the backup files back to their previous location using:
```
mv /tmp/meilisearch /path/to/your/meilisearch/directory/meilisearch
mv /tmp/data.ms /path/to/your/meilisearch/directory/meilisearch/data.ms
```
```
mv /tmp/meilisearch /usr/bin/meilisearch
mv /tmp/data.ms /var/lib/meilisearch/data.ms
```
For **cloud platforms** run the configuration script at the root of your Meilisearch directory:
```
meilisearch-setup
```
If all went well, you can delete the backup files using:
```
rm -r /tmp/meilisearch
rm -r /tmp/data.ms
```
You can also delete the dump file if desired:
```
rm /path/to/your/meilisearch/directory/meilisearch/dumps/{dump_uid.dump}
```
```
rm /var/opt/meilisearch/dumps/{dump_uid.dump}
```
## Version-specific warnings
After migrating to the most recent version of Meilisearch, your codebase may require some changes. For version-specific changes and full changelogs, see the [releases tab on GitHub](https://github.com/meilisearch/meilisearch/releases).
# Configure Meilisearch at launch
Source: https://www.meilisearch.com/docs/resources/self_hosting/configuration/overview
Configure Meilisearch at launch with command-line options, environment variables, or a configuration file.
When self-hosting Meilisearch, you can configure your instance at launch with **command-line options**, **environment variables**, or a **configuration file**.
These startup options affect your entire Meilisearch instance, not just a single index. For settings that affect search within a single index, see [index settings](/docs/reference/api/settings/list-all-settings).
## Configuration methods
Meilisearch supports three configuration methods. When used simultaneously, **command-line options** take the highest precedence, followed by **environment variables**, and finally the **configuration file**.
### Command-line options and flags
Pass command-line options and their respective values when launching a Meilisearch instance:
```bash theme={null}
./meilisearch --db-path ./meilifiles --http-addr 'localhost:7700'
```
Meilisearch also has **command-line flags** that don't take values. If a flag is given, it activates and changes default behavior:
```bash theme={null}
./meilisearch --no-analytics
```
All command-line options and flags are prepended with `--`. They take precedence over environment variables.
### Environment variables
Set environment variables prior to launching the instance:
```sh theme={null}
export MEILI_DB_PATH=./meilifiles
export MEILI_HTTP_ADDR=localhost:7700
./meilisearch
```
```sh theme={null}
set MEILI_DB_PATH=./meilifiles
set MEILI_HTTP_ADDR=127.0.0.1:7700
./meilisearch
```
Environment variables for flags accept `n`, `no`, `f`, `false`, `off`, and `0` as `false`. An absent environment variable is also considered `false`. Any other value is considered `true`.
Environment variables are always identical to the corresponding command-line option, but prepended with `MEILI_` and written in all uppercase.
### Configuration file
Meilisearch accepts a configuration file in `.toml` format. Configuration files can be easily shared, versioned, and allow you to define multiple options.
Download a default configuration file:
```sh theme={null}
curl https://raw.githubusercontent.com/meilisearch/meilisearch/latest/config.toml > config.toml
```
By default, Meilisearch looks for a `config.toml` file in the working directory. You can override this with the `MEILI_CONFIG_FILE_PATH` environment variable or the `--config-file-path` CLI option:
```sh theme={null}
./meilisearch --config-file-path="./config.toml"
```
UNIX:
```sh theme={null}
export MEILI_CONFIG_FILE_PATH="./config.toml"
./meilisearch
```
Windows:
```sh theme={null}
set MEILI_CONFIG_FILE_PATH="./config.toml"
./meilisearch
```
In configuration files, options must be written in [snake case](https://en.wikipedia.org/wiki/Snake_case). For example, `--import-dump` would be written as `import_dump`.
Specifying the `config_file_path` option within the configuration file will throw an error.
## Configuring cloud-hosted instances
To configure Meilisearch with command-line options in a cloud-hosted instance, edit its [service file](/docs/resources/self_hosting/deployment/running_production#step-4-run-meilisearch-as-a-service). The default location of the service file is `/etc/systemd/system/meilisearch.service`.
To configure Meilisearch with environment variables in a cloud-hosted instance, modify Meilisearch's `env` file. Its default location is `/var/opt/meilisearch/env`.
After editing your configuration options, relaunch the Meilisearch service:
```sh theme={null}
systemctl restart meilisearch
```
[Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=instance-options) offers an optimal pre-configured environment. You do not need to use any of the configuration options listed here when hosting your project on Meilisearch Cloud.
## Next steps
Complete list of all instance configuration options.
Configure search behavior for individual indexes.
# Configuration reference
Source: https://www.meilisearch.com/docs/resources/self_hosting/configuration/reference
Complete reference of all Meilisearch instance configuration options, environment variables, and CLI flags.
This page lists all available Meilisearch instance configuration options. For an introduction to configuration methods, see [Configuration overview](/docs/resources/self_hosting/configuration/overview).
## All instance options
### Configuration file path
**Environment variable**: `MEILI_CONFIG_FILE_PATH`
**CLI option**: `--config-file-path`
**Default**: `./config.toml`
**Expected value**: a filepath
Designates the location of the configuration file to load at launch.
Specifying this option in the configuration file itself will throw an error (assuming Meilisearch is able to find your configuration file).
### Database path
**Environment variable**: `MEILI_DB_PATH`
**CLI option**: `--db-path`
**Default value**: `"data.ms/"`
**Expected value**: a filepath
Designates the location where database files will be created and retrieved.
### Environment
**Environment variable**: `MEILI_ENV`
**CLI option**: `--env`
**Default value**: `development`
**Expected value**: `production` or `development`
Configures the instance's environment. Value must be either `production` or `development`.
`production`:
* Setting a [master key](/docs/resources/self_hosting/security/basic_security) of at least 16 bytes is **mandatory**. If no master key is provided or if it is under 16 bytes, Meilisearch will suggest a secure autogenerated master key
* The [search preview interface](/docs/resources/self_hosting/getting_started/search_preview) is disabled
`development`:
* Setting a [master key](/docs/resources/self_hosting/security/basic_security) is **optional**. If no master key is provided or if it is under 16 bytes, Meilisearch will suggest a secure autogenerated master key
* Search preview is enabled
When the server environment is set to `development`, providing a master key is not mandatory. This is useful when debugging and prototyping, but dangerous otherwise since API routes are unprotected.
### HTTP address & port binding
**Environment variable**: `MEILI_HTTP_ADDR`
**CLI option**: `--http-addr`
**Default value**: `"localhost:7700"`
**Expected value**: an HTTP address and port
Sets the HTTP address and port Meilisearch will use.
### Master key
**Environment variable**: `MEILI_MASTER_KEY`
**CLI option**: `--master-key`
**Default value**: `None`
**Expected value**: a UTF-8 string of at least 16 bytes
Sets the instance's master key, automatically protecting all routes except [`GET /health`](/docs/reference/api/management/get-health). This means you will need a valid API key to access all other endpoints.
When `--env` is set to `production`, providing a master key is mandatory. If none is given, or it is under 16 bytes, Meilisearch will throw an error and refuse to launch.
When `--env` is set to `development`, providing a master key is optional. If none is given, all routes will be unprotected and publicly accessible.
If you do not supply a master key in `production` or `development` environments or it is under 16 bytes, Meilisearch will suggest a secure autogenerated master key you can use when restarting your instance.
[Learn more about Meilisearch's use of security keys.](/docs/resources/self_hosting/security/basic_security)
### Disable analytics
🚩 This option does not take any values. Assigning a value will throw an error. 🚩
**Environment variable**: `MEILI_NO_ANALYTICS`
**CLI option**: `--no-analytics`
Deactivates Meilisearch's built-in telemetry when provided.
Meilisearch automatically collects data from all instances that do not opt out using this flag. All gathered data is used solely for the purpose of improving Meilisearch, and can be [deleted at any time](/docs/resources/help/telemetry#how-to-delete-all-collected-data).
[Read more about our policy on data collection](/docs/resources/help/telemetry), or take a look at [the comprehensive list of all data points we collect](/docs/resources/help/telemetry#exhaustive-list-of-all-collected-data).
### Dumpless upgrade
**Environment variable**: `MEILI_UPGRADE_DB`
**CLI option**: `--upgrade-db`
**Default value**: None
**Expected value**: None
Migrates the database to a new Meilisearch version after you have manually updated the binary.
[Learn more about updating Meilisearch to a new release](/docs/resources/migration/updating).
Before Meilisearch v1.51, this option was named `--experimental-dumpless-upgrade` (environment variable: `MEILI_EXPERIMENTAL_DUMPLESS_UPGRADE`).
#### Create a snapshot before a dumpless upgrade
Take a snapshot of your instance before performing a dumpless upgrade.
Dumpless upgrades are not currently atomic. It is possible some processes fail and Meilisearch still finalizes the upgrade. This may result in a corrupted database and data loss.
### Dump directory
**Environment variable**: `MEILI_DUMP_DIR`
**CLI option**: `--dump-dir`
**Default value**: `dumps/`
**Expected value**: a filepath pointing to a valid directory
Sets the directory where Meilisearch will create dump files.
`--dump-dir` only controls where the final compressed `.dump` file is written. While creating a dump, Meilisearch first builds an uncompressed copy of your data in a temporary staging directory. This directory is located in the path indicated by the `TMPDIR` environment variable, defaulting to `/tmp` on most systems, and not in `--dump-dir`. On instances with a large database and a small `/tmp` partition, this can cause `No space left on device` errors even when `--dump-dir` points to a volume with plenty of free space. To avoid this, set `TMPDIR` to a directory on a volume with enough space for an uncompressed copy of your data before launching Meilisearch.
[Learn more about creating dumps](/docs/reference/api/management/create-dump).
### Import dump
**Environment variable**: `MEILI_IMPORT_DUMP`
**CLI option**: `--import-dump`
**Default value**: none
**Expected value**: a filepath pointing to a `.dump` file
Imports the dump file located at the specified path. Path must point to a `.dump` file. If a database already exists, Meilisearch will throw an error and abort launch.
Meilisearch will only launch once the dump data has been fully indexed. The time this takes depends on the size of the dump file.
### Ignore missing dump
🚩 This option does not take any values. Assigning a value will throw an error. 🚩
**Environment variable**: `MEILI_IGNORE_MISSING_DUMP`
**CLI option**: `--ignore-missing-dump`
Prevents Meilisearch from throwing an error when `--import-dump` does not point to a valid dump file. Instead, Meilisearch will start normally without importing any dump.
This option will trigger an error if `--import-dump` is not defined.
### Ignore dump if DB exists
**Environment variable**: `MEILI_IGNORE_DUMP_IF_DB_EXISTS`
**CLI option**: `--ignore-dump-if-db-exists`
**Expected value**: a boolean (`true` or `false`)
Set this option to `true` to prevent a Meilisearch instance with an existing database from throwing an error when using `--import-dump`. When enabled, the dump will be ignored and Meilisearch will launch using the existing database.
For the environment variable, set `MEILI_IGNORE_DUMP_IF_DB_EXISTS=true`. For the CLI option, pass `--ignore-dump-if-db-exists`.
This option will trigger an error if `--import-dump` is not defined.
### Log level
**Environment variable**: `MEILI_LOG_LEVEL`
**CLI option**: `--log-level`
**Default value**: `'INFO'`
**Expected value**: one of `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE`, OR `OFF`
Defines how much detail should be present in Meilisearch's logs.
Meilisearch currently supports five log levels, listed in order of increasing verbosity:
* `'ERROR'`: only log unexpected events indicating Meilisearch is not functioning as expected
* `'WARN'`: log all unexpected events, regardless of their severity
* `'INFO'`: log all events. This is the default value of `--log-level`
* `'DEBUG'`: log all events and include detailed information on Meilisearch's internal processes. Useful when diagnosing issues and debugging
* `'TRACE'`: log all events and include even more detailed information on Meilisearch's internal processes. We do not advise using this level as it is extremely verbose. Use `'DEBUG'` before considering `'TRACE'`.
* `'OFF'`: disable logging
### Customize log output
**Environment variable**: `MEILI_EXPERIMENTAL_LOGS_MODE`
**CLI option**: `--experimental-logs-mode`
**Default value**: `'human'`
**Expected value**: one of `human` or `json`
Defines whether logs should output a human-readable text or JSON data.
### Max indexing memory
**Environment variable**: `MEILI_MAX_INDEXING_MEMORY`
**CLI option**: `--max-indexing-memory`
**Default value**: 2/3 of the available RAM
**Expected value**: an integer (`104857600`) or a human readable size (`'100Mb'`)
Sets the maximum amount of RAM Meilisearch can use when indexing. By default, Meilisearch uses no more than two thirds of available memory.
The value must either be given in bytes or explicitly state a base unit: `107374182400`, `'107.7Gb'`, or `'107374 Mb'`.
It is possible that Meilisearch goes over the exact RAM limit during indexing. In most contexts and machines, this should be a negligible amount with little to no impact on stability and performance.
Setting `--max-indexing-memory` to a value bigger than or equal to your machine's total memory is likely to cause your instance to crash.
### Reduce indexing memory usage
🚩 This option does not take any values. Assigning a value will throw an error. 🚩
**Environment variable**: `MEILI_EXPERIMENTAL_REDUCE_INDEXING_MEMORY_USAGE`
**CLI option**: `--experimental-reduce-indexing-memory-usage`
**Default value**: `None`
Enables `MDB_WRITEMAP`, an LMDB option. Activating this option may reduce RAM usage in some UNIX and UNIX-like setups. However, it may also negatively impact write speeds and overall performance.
### Max indexing threads
**Environment variable**: `MEILI_MAX_INDEXING_THREADS`
**CLI option**: `--max-indexing-threads`
**Default value**: half of the available threads
**Expected value**: an integer
Sets the maximum number of threads Meilisearch can use during indexing. By default, the indexer avoids using more than half of a machine's total processing units. This ensures Meilisearch is always ready to perform searches, even while you are updating an index.
If `--max-indexing-threads` is higher than the real number of cores available in the machine, Meilisearch uses the maximum number of available cores.
In single-core machines, Meilisearch has no choice but to use the only core available for indexing. This may lead to a degraded search experience during indexing.
Avoid setting `--max-indexing-threads` to the total of your machine's processor cores. Though doing so might speed up indexing, it is likely to severely impact search experience.
### Payload limit size
**Environment variable**: `MEILI_HTTP_PAYLOAD_SIZE_LIMIT`
**CLI option**: `--http-payload-size-limit`
**Default value**: `100000000` (\~100MB)
**Expected value**: an integer
Sets the maximum size of [accepted payloads](/docs/resources/internals/documents#dataset-format). Value must be given in bytes or explicitly stating a base unit. For example, the default value can be written as `100000000`, `'100Mb'`, or `'100 MB'`.
### Search queue size
**Environment variable**: `MEILI_EXPERIMENTAL_SEARCH_QUEUE_SIZE`
**CLI option**: `--experimental-search-queue-size`
**Default value**: `1000`
**Expected value**: an integer
Configure the maximum amount of simultaneous search requests. By default, Meilisearch queues up to 1000 search requests at any given moment. This limit exists to prevent Meilisearch from consuming an unbounded amount of RAM.
### Search query embedding cache
**Environment variable**: `MEILI_EXPERIMENTAL_EMBEDDING_CACHE_ENTRIES`
**CLI option**: `--experimental-embedding-cache-entries`
**Default value**: `0`
**Expected value**: an integer
Sets the size of the search query embedding cache. By default, Meilisearch generates an embedding for every new search query. When this option is set to an integer bigger than 0, Meilisearch returns a previously generated embedding if it recently performed the same query.
The least recently used entries are evicted first. Embedders with the same configuration share the same cache, even if they were declared in distinct indexes.
### Schedule snapshot creation
**Environment variable**: `MEILI_SCHEDULE_SNAPSHOT`
**CLI option**: `--schedule-snapshot`
**Default value**: disabled if not present, `86400` if present without a value
**Expected value**: `None` or an integer
Activates scheduled snapshots. Snapshots are disabled by default.
It is possible to use `--schedule-snapshot` without a value. If `--schedule-snapshot` is present when launching an instance but has not been assigned a value, Meilisearch takes a new snapshot every 24 hours.
For more control over snapshot scheduling, pass an integer representing the interval in seconds between each snapshot. When `--schedule-snapshot=3600`, Meilisearch takes a new snapshot every hour. Meilisearch waits for the configured interval before creating the first scheduled snapshot.
When using the configuration file, it is also possible to explicitly pass a boolean value to `schedule_snapshot`. Meilisearch takes a new snapshot every 24 hours when `schedule_snapshot=true`, and takes no snapshots when `schedule_snapshot=false`.
[Learn more about snapshots](/docs/resources/self_hosting/data_backup/snapshots).
### Snapshot destination
**Environment variable**: `MEILI_SNAPSHOT_DIR`
**CLI option**: `--snapshot-dir`
**Default value**: `snapshots/`
**Expected value**: a filepath pointing to a valid directory
Sets the directory where Meilisearch will store snapshots.
`--snapshot-dir` only controls where the final `.snapshot` file is written. While creating a snapshot, Meilisearch first copies your entire database (the raw database files) into a temporary staging directory. This directory is located in the path indicated by the `TMPDIR` environment variable, defaulting to `/tmp` on most systems, and not in `--snapshot-dir`. On instances with a large database and a small `/tmp` partition, this can cause `No space left on device` errors even when `--snapshot-dir` points to a volume with plenty of free space. To avoid this, set `TMPDIR` to a directory on a volume with enough space for a full copy of your database before launching Meilisearch.
### Import snapshot
**Environment variable**: `MEILI_IMPORT_SNAPSHOT`
**CLI option**: `--import-snapshot`
**Default value**: `None`
**Expected value**: a filepath pointing to a snapshot file
Launches Meilisearch after importing a previously-generated snapshot at the given filepath.
This command will throw an error if:
* A database already exists
* No valid snapshot can be found in the specified path
This behavior can be modified with the [`--ignore-snapshot-if-db-exists`](#ignore-snapshot-if-db-exists) and [`--ignore-missing-snapshot`](#ignore-missing-snapshot) options, respectively.
### Ignore missing snapshot
🚩 This option does not take any values. Assigning a value will throw an error. 🚩
**Environment variable**: `MEILI_IGNORE_MISSING_SNAPSHOT`
**CLI option**: `--ignore-missing-snapshot`
Prevents a Meilisearch instance from throwing an error when [`--import-snapshot`](#import-snapshot) does not point to a valid snapshot file.
This command will throw an error if `--import-snapshot` is not defined.
### Ignore snapshot if DB exists
🚩 This option does not take any values. Assigning a value will throw an error. 🚩
**Environment variable**: `MEILI_IGNORE_SNAPSHOT_IF_DB_EXISTS`
**CLI option**: `--ignore-snapshot-if-db-exists`
Prevents a Meilisearch instance with an existing database from throwing an error when using `--import-snapshot`. Instead, the snapshot will be ignored and Meilisearch will launch using the existing database.
This command will throw an error if `--import-snapshot` is not defined.
### Task webhook URL
**Environment variable**: `MEILI_TASK_WEBHOOK_URL`
**CLI option**: `--task-webhook-url`
**Default value**: `None`
**Expected value**: a URL string
Notifies the configured URL whenever Meilisearch [finishes processing a task](/docs/capabilities/indexing/tasks_and_batches/async_operations#task-status) or batch of tasks. Meilisearch uses the URL as given, retaining any specified query parameters.
The webhook payload contains the list of finished tasks in [ndjson](https://github.com/ndjson/ndjson-spec). For more information, [consult the dedicated task webhook guide](/docs/resources/self_hosting/webhooks).
The task webhook option requires having access to a command-line interface. If you are using Meilisearch Cloud, use the [`/webhooks` API route](/docs/reference/api/management/list-webhooks) instead.
### Task webhook authorization header
**Environment variable**: `MEILI_TASK_WEBHOOK_AUTHORIZATION_HEADER`
**CLI option**: `--task-webhook-authorization-header`
**Default value**: `None`
**Expected value**: an authentication token string
Includes an authentication token in the authorization header when notifying the [webhook URL](#task-webhook-url).
### Maximum number of batched tasks
**Environment variable**: `MEILI_EXPERIMENTAL_MAX_NUMBER_OF_BATCHED_TASKS`
**CLI option**: `--experimental-max-number-of-batched-tasks`
**Default value**: unlimited
**Expected value**: an integer
Limit the number of tasks Meilisearch performs in a single batch. May improve stability in systems handling a large queue of resource-intensive tasks.
### Maximum batch payload size
**Environment variable**: `MEILI_EXPERIMENTAL_LIMIT_BATCHED_TASKS_TOTAL_SIZE`
**CLI option**: `--experimental-limit-batched-tasks-total-size`
**Default value**: Half of total available memory, up to a maximum of 10 GiB
**Expected value**: an integer
Sets a maximum payload size for batches in bytes. Smaller batches are less efficient, but consume less RAM and reduce immediate latency.
### Disable new indexer
🚩 This option does not take any values. Assigning a value will throw an error. 🚩
**Environment variable**: `MEILI_EXPERIMENTAL_NO_EDITION_2024_FOR_SETTINGS`
**CLI option**: `--experimental-no-edition-2024-for-settings`
**Default value**: `None`
Falls back to previous settings indexer.
### Enable logs route
🚩 This option does not take any values. Assigning a value will throw an error. 🚩
**Environment variable**: `MEILI_EXPERIMENTAL_ENABLE_LOGS_ROUTE`
**CLI option**: `--experimental-enable-logs-route`
**Default value**: `None`
Enables the `/logs/stream`, `/logs/stderr` and `DELETE /logs/stream` routes for log streaming and configuration.
### Enable metrics
🚩 This option does not take any values. Assigning a value will throw an error. 🚩
**Environment variable**: `MEILI_EXPERIMENTAL_ENABLE_METRICS`
**CLI option**: `--experimental-enable-metrics`
**Default value**: `None`
Enables the Prometheus `/metrics` endpoint for monitoring.
### CONTAINS filter operator
🚩 This option does not take any values. Assigning a value will throw an error. 🚩
**Environment variable**: `MEILI_EXPERIMENTAL_CONTAINS_FILTER`
**CLI option**: `--experimental-contains-filter`
**Default value**: `None`
Enables the `CONTAINS` filter operator at launch. It can also be toggled at runtime via the `/experimental-features` API route.
### Drop search after
**Environment variable**: `MEILI_EXPERIMENTAL_DROP_SEARCH_AFTER`
**CLI option**: `--experimental-drop-search-after`
**Default value**: `60`
**Expected value**: an integer (seconds)
Sets the maximum time in seconds a search request can take before being dropped. Helps prevent slow searches from blocking resources.
### Searches per core
**Environment variable**: `MEILI_EXPERIMENTAL_NB_SEARCHES_PER_CORE`
**CLI option**: `--experimental-nb-searches-per-core`
**Default value**: `4`
**Expected value**: an integer
Configures the number of concurrent search requests each CPU core can handle.
### Search personalization
**Environment variable**: `MEILI_EXPERIMENTAL_PERSONALIZATION_API_KEY`
**CLI option**: `--experimental-personalization-api-key`
**Default value**: `None`
**Expected value**: a Cohere API key
Enables search personalization. Must be a valid Cohere API key in string format.
### Allow requests to private networks
**Environment variable**: `MEILI_EXPERIMENTAL_ALLOWED_IP_NETWORKS`
**CLI option**: `--experimental-allowed-ip-networks`
**Default value**: `None`
**Expected value**: a list of comma-separated CIDR networks
Allow Meilisearch to query services running on private networks.
By default, Meilisearch will prevent any requests to a host resolving to a non-global IP, in the sense of
the [IANA IPv4 Special-Purpose Address Registry](https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml)
or the [IANA IPv6 Special-Purpose Address Registry](https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml).
This is done to prevent potential firewall bypasses (see our [blog post](https://www.meilisearch.com/blog/CVE-update-Jan-2026) on SSRF vulnerability).
You may want to allow requests to private networks to query internal services during webhooks or use local embedding services.
To do so, specify a list of comma-separated CIDR networks (e.g. `192.168.0.0/16,10.0.0.0/8`). You may specify `any` to allow all
requests regardless of target IP (use only in controlled environments, this is not recommended for production).
### Remote search timeout
**Environment variable**: `MEILI_EXPERIMENTAL_REMOTE_SEARCH_TIMEOUT_SECONDS`
**Default value**: `30`
**Expected value**: a positive integer (seconds)
Sets the maximum time in seconds a remote federated search request can take before timing out. This configuration is only available via environment variable; no CLI flag is available.
### Disable FID-based database cleanup
**Environment variable**: `MEILI_EXPERIMENTAL_DISABLE_FID_BASED_DATABASES_CLEANUP`
**Default value**: `false`
**Expected value**: a boolean
Allows you to opt out of the field ID-based database cleanup when upgrading from Meilisearch versions prior to v1.32. Set this to `true` if you experience issues during the upgrade process. This configuration is only available via environment variable; no CLI flag is available.
### S3 options
S3 snapshot storage requires the Meilisearch Enterprise Edition. See [Enterprise and Community editions](/docs/resources/self_hosting/enterprise_edition) for details.
#### Bucket URL
**Environment variable**: `MEILI_S3_BUCKET_URL`
**CLI option**: `--s3-bucket-url`
**Default value**: `None`
The URL for your S3 bucket. The URL must follow the format `https://s3.REGION.amazonaws.com`.
#### Bucket region
**Environment variable**: `MEILI_S3_BUCKET_REGION`
**CLI option**: `--s3-bucket-region`
**Default value**: `None`
The region of your S3 bucket. Must be a valid AWS region, such as `us-east-1`.
#### Bucket name
**Environment variable**: `MEILI_S3_BUCKET_NAME`
**CLI option**: `--s3-bucket-name`
**Default value**: `None`
The name of your S3 bucket.
#### Snapshot prefix
**Environment variable**: `MEILI_S3_SNAPSHOT_PREFIX`
**CLI option**: `--s3-snapshot-prefix`
**Default value**: `None`
The path leading to the [snapshot directory](#snapshot-destination) in your S3 bucket. Uses normal slashes.
#### Access key
**Environment variable**: `MEILI_S3_ACCESS_KEY`
**CLI option**: `--s3-access-key`
**Default value**: `None`
Your S3 bucket's access key.
#### Secret key
**Environment variable**: `MEILI_S3_SECRET_KEY`
**CLI option**: `--s3-secret-key`
**Default value**: `None`
Your S3 bucket's secret key.
#### Role ARN
**Environment variable**: `MEILI_EXPERIMENTAL_S3_ROLE_ARN`
**CLI option**: `--experimental-s3-role-arn`
**Default value**: `None`
IAM role ARN for web identity federation. Use this instead of access key and secret key for authentication. Cannot be combined with `--s3-access-key` and `--s3-secret-key`.
#### Web identity token file
**Environment variable**: `MEILI_EXPERIMENTAL_S3_WEB_IDENTITY_TOKEN_FILE`
**CLI option**: `--experimental-s3-web-identity-token-file`
**Default value**: `None`
Path to the web identity token file for S3 authentication via web identity federation. Cannot be combined with `--s3-access-key` and `--s3-secret-key`.
#### Maximum parallel in-flight requests
**Environment variable**: `MEILI_EXPERIMENTAL_S3_MAX_IN_FLIGHT_PARTS`
**CLI option**: `--experimental-s3-max-in-flight-parts`
**Default value**: `10`
The maximum number of in-flight multipart requests Meilisearch should send to S3 in parallel.
#### Compression level
**Environment variable**: `MEILI_EXPERIMENTAL_S3_COMPRESSION_LEVEL`
**CLI option**: `--experimental-s3-compression-level`
**Default value**: `0`
The compression level to use for the snapshot tarball. Defaults to 0, no compression.
#### Signature duration
**Environment variable**: `MEILI_EXPERIMENTAL_S3_SIGNATURE_DURATION_SECONDS`
**CLI option**: `--experimental-s3-signature-duration-seconds`
**Default value**: `28800`
The maximum duration processing a snapshot can take. Defaults to 8 hours.
#### Multipart section size
**Environment variable**: `MEILI_EXPERIMENTAL_S3_MULTIPART_PART_SIZE`
**CLI option**: `--experimental-s3-multipart-part-size`
**Default value**: `None`
The size of each multipart section. Must be >10MiB and \<8GiB. Defaults to 375MiB, which enables databases of up to 3.5TiB.
### SSL options
#### SSL authentication path
**Environment variable**: `MEILI_SSL_AUTH_PATH`
**CLI option**: `--ssl-auth-path`
**Default value**: `None`
**Expected value**: a filepath
Enables client authentication in the specified path.
#### SSL certificates path
**Environment variable**: `MEILI_SSL_CERT_PATH`
**CLI option**: `--ssl-cert-path`
**Default value**: `None`
**Expected value**: a filepath pointing to a valid SSL certificate
Sets the server's SSL certificates.
Value must be a path to PEM-formatted certificates. The first certificate should certify the KEYFILE supplied by `--ssl-key-path`. The last certificate should be a root CA.
#### SSL key path
**Environment variable**: `MEILI_SSL_KEY_PATH`
**CLI option**: `--ssl-key-path`
**Default value**: `None`
**Expected value**: a filepath pointing to a valid SSL key file
Sets the server's SSL key files.
Value must be a path to an RSA private key or PKCS8-encoded private key, both in PEM format.
#### SSL OCSP path
**Environment variable**: `MEILI_SSL_OCSP_PATH`
**CLI option**: `--ssl-ocsp-path`
**Default value**: `None`
**Expected value**: a filepath pointing to a valid OCSP certificate
Sets the server's OCSP file. *Optional*
Reads DER-encoded OCSP response from OCSPFILE and staple to certificate.
#### SSL require auth
🚩 This option does not take any values. Assigning a value will throw an error. 🚩
**Environment variable**: `MEILI_SSL_REQUIRE_AUTH`
**CLI option**: `--ssl-require-auth`
**Default value**: `None`
Makes SSL authentication mandatory.
Sends a fatal alert if the client does not complete client authentication.
#### SSL resumption
🚩 This option does not take any values. Assigning a value will throw an error. 🚩
**Environment variable**: `MEILI_SSL_RESUMPTION`
**CLI option**: `--ssl-resumption`
**Default value**: `None`
Activates SSL session resumption.
#### SSL tickets
🚩 This option does not take any values. Assigning a value will throw an error. 🚩
**Environment variable**: `MEILI_SSL_TICKETS`
**CLI option**: `--ssl-tickets`
**Default value**: `None`
Activates SSL tickets.
# Exporting and importing dumps
Source: https://www.meilisearch.com/docs/resources/self_hosting/data_backup/dumps
Dumps are data backups containing all data related to a Meilisearch instance. They are often useful when migrating to a new Meilisearch release.
A [dump](/docs/resources/self_hosting/data_backup/overview#dumps) is a compressed file containing an export of your Meilisearch instance. Use dumps to migrate to new Meilisearch versions. This tutorial shows you how to create and import dumps.
Creating a dump is also referred to as exporting it. Launching Meilisearch with a dump is referred to as importing it.
## Creating a dump
### Creating a dump in Meilisearch Cloud
**You cannot manually export dumps in Meilisearch Cloud**. To [migrate your project to the most recent Meilisearch release](/docs/resources/migration/updating), use the Cloud interface:
If you need to create a dump for reasons other than upgrading, contact the support team via the Meilisearch Cloud interface or the [official Meilisearch Discord server](https://discord.meilisearch.com).
### Creating a dump in a self-hosted instance
To create a dump, use the [create a dump endpoint](/docs/reference/api/management/create-dump):
```bash cURL theme={null}
curl \
-X POST 'MEILISEARCH_URL/dumps'
```
```javascript JS theme={null}
client.createDump()
```
```python Python theme={null}
client.create_dump()
```
```php PHP theme={null}
$client->createDump();
```
```java Java theme={null}
client.createDump();
```
```ruby Ruby theme={null}
client.create_dump
```
```go Go theme={null}
resp, err := client.CreateDump()
```
```csharp C# theme={null}
await client.CreateDumpAsync();
```
```rust Rust theme={null}
client
.create_dump()
.await
.unwrap();
```
```swift Swift theme={null}
client.createDump { result in
switch result {
case .success(let dumpStatus):
print(dumpStatus)
case .failure(let error):
print(error)
}
}
```
```dart Dart theme={null}
await client.createDump();
```
This will return a [summarized task object](/docs/reference/api/tasks/get-task) that you can use to check the status of your dump.
```json theme={null}
{
"taskUid": 1,
"indexUid": null,
"status": "enqueued",
"type": "dumpCreation",
"enqueuedAt": "2022-06-21T16:10:29.217688Z"
}
```
The dump creation process is an asynchronous task that takes time proportional to the size of your database. Replace `1` with the `taskUid` returned by the previous command:
```bash cURL theme={null}
curl \
-X GET 'MEILISEARCH_URL/tasks/1'
```
```javascript JS theme={null}
client.tasks.getTask(1)
```
```python Python theme={null}
client.get_task(1)
```
```php PHP theme={null}
$client->getTask(1);
```
```java Java theme={null}
client.getTask(1);
```
```ruby Ruby theme={null}
client.task(1)
```
```go Go theme={null}
client.GetTask(1);
```
```csharp C# theme={null}
TaskInfo task = await client.GetTaskAsync(1);
```
```rust Rust theme={null}
let task: Task = client
.get_task(1)
.await
.unwrap();
```
```swift Swift theme={null}
client.getTask(taskUid: 1) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart theme={null}
await client.getTask(1);
```
This should return an object with detailed information about the dump operation:
```json theme={null}
{
"uid": 1,
"indexUid": null,
"status": "succeeded",
"type": "dumpCreation",
"canceledBy": null,
"details": {
"dumpUid": "20220621-161029217"
},
"error": null,
"duration": "PT0.025872S",
"enqueuedAt": "2022-06-21T16:10:29.217688Z",
"startedAt": "2022-06-21T16:10:29.218297Z",
"finishedAt": "2022-06-21T16:10:29.244169Z"
}
```
All indexes of the current instance are exported along with their documents and settings and saved as a single `.dump` file. The dump also includes any tasks registered before Meilisearch starts processing the dump creation task.
Once the task `status` changes to `succeeded`, find the dump file in [the dump directory](/docs/resources/self_hosting/configuration/reference#dump-directory). By default, this folder is named `dumps` and can be found in the same directory where you launched Meilisearch.
If a dump file is visible in the file system, the dump process was successfully completed. **Meilisearch will never create a partial dump file**, even if you interrupt an instance while it is generating a dump.
Since the `key` field depends on the master key, it is not propagated to dumps. If a malicious user ever gets access to your dumps, they will not have access to your instance's API keys.
## Importing a dump
Import a dump by launching a Meilisearch instance with the [`--import-dump` configuration option](/docs/resources/self_hosting/configuration/reference#import-dump):
```bash theme={null}
./meilisearch --import-dump /dumps/20200813-042312213.dump
```
Depending on the size of your dump file, importing it might take a significant amount of time. You will only be able to access Meilisearch and its API once this process is complete.
Meilisearch imports all data in the dump file. If you have already added data to your instance, existing indexes with the same `uid` as an index in the dump file will be overwritten.
Do not use dumps to migrate from a new Meilisearch version to an older release. Doing so might lead to unexpected behavior.
# Backing up Meilisearch data
Source: https://www.meilisearch.com/docs/resources/self_hosting/data_backup/overview
Meilisearch offers two backup methods: snapshots for periodic safeguards and dumps for version migration. Learn when to use each.
Meilisearch offers two backup methods: **snapshots** and **dumps**. They serve different purposes and have different trade-offs.
## Snapshots vs dumps
| | Snapshots | Dumps |
| ------------------------- | -------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| **What it is** | Exact copy of the database (`data.ms`) | Portable blueprint of all instance data |
| **Import speed** | Fast (data is already indexed) | Slow (requires full re-indexing) |
| **Version compatibility** | Same Meilisearch version only | Compatible across versions |
| **File size** | Larger | Smaller |
| **Best for** | Periodic backups, disaster recovery | Migrating to a new Meilisearch version |
| **Scheduling** | Can be [scheduled at launch](/docs/resources/self_hosting/configuration/reference#schedule-snapshot-creation) | Created on demand via API |
| **Cloud support** | Self-hosted only | Cloud (via UI) and self-hosted (via API) |
## When to use snapshots
Use snapshots as a safeguard. If something goes wrong, you can recover and relaunch your database quickly. You can schedule periodic snapshot creation at launch.
[Learn how to create and import snapshots](/docs/resources/self_hosting/data_backup/snapshots).
## When to use dumps
Use dumps when migrating data between Meilisearch versions. Dumps are not bound to a specific version, so they are ideal for upgrades.
You can import dumps from older Meilisearch versions into newer ones. Importing a dump from a newer version into an older one can lead to unexpected behavior.
[Learn how to create and import dumps](/docs/resources/self_hosting/data_backup/dumps).
## Backup recommendations
* **Schedule snapshots** for regular backups. A daily snapshot (`--schedule-snapshot=86400`) is a good starting point.
* **Create a dump before upgrading** Meilisearch to a new version.
* **Test your restore process** periodically to make sure backups work.
* **Store backups off-server** using [S3 snapshot storage](/docs/resources/self_hosting/configuration/reference#s3-options) or by copying dump files to external storage.
# Exporting and using Snapshots
Source: https://www.meilisearch.com/docs/resources/self_hosting/data_backup/snapshots
Snapshots are exact copies of Meilisearch databases. They are often useful for periodical backups.
A [snapshot](/docs/resources/self_hosting/data_backup/overview#snapshots) is an exact copy of the Meilisearch database. Snapshots are useful as quick backups, but cannot be used to migrate to a new Meilisearch release.
This tutorial shows you how to schedule snapshot creation to ensure you always have a recent backup of your instance ready to use. You will also see how to start Meilisearch from this snapshot.
Meilisearch Cloud does not support snapshots.
## Scheduling periodic snapshots
It is good practice to create regular backups of your Meilisearch data. This ensures that you can recover from critical failures quickly in case your Meilisearch instance becomes compromised.
Use the [`--schedule-snapshot` configuration option](/docs/resources/self_hosting/configuration/reference#schedule-snapshot-creation) to create snapshots at regular time intervals:
```bash theme={null}
meilisearch --schedule-snapshot
```
After launch, Meilisearch waits for the configured interval before creating the first snapshot. You will find it in the [snapshot directory](/docs/resources/self_hosting/configuration/reference#snapshot-destination), `snapshots/`. Meilisearch will then create a new snapshot every 24 hours until you terminate your instance.
Meilisearch **automatically overwrites** old snapshots during snapshot creation. Only the most recent snapshot will be present in the folder at any given time.
In cases where your database is updated several times a day, it might be better to modify the interval between each new snapshot:
```bash theme={null}
meilisearch --schedule-snapshot=3600
```
This instructs Meilisearch to create a new snapshot once every hour.
If you need to generate a single snapshot without relaunching your instance, use [the `/snapshots` route](/docs/reference/api/management/create-snapshot).
## Starting from a snapshot
To import snapshot data into your instance, launch Meilisearch using `--import-snapshot`:
```bash theme={null}
meilisearch --import-snapshot mySnapShots/data.ms.snapshot
```
Because snapshots are exact copies of your database, starting a Meilisearch instance from a snapshot is much faster than adding documents manually or starting from a dump.
For security reasons, Meilisearch will never overwrite an existing database. By default, Meilisearch will throw an error when importing a snapshot if there is any data in your instance.
You can change this behavior by specifying [`--ignore-snapshot-if-db-exists=true`](/docs/resources/self_hosting/configuration/reference#ignore-snapshot-if-db-exists). This will cause Meilisearch to launch with the existing database and ignore the snapshot without throwing an error.
# Deploy on AWS
Source: https://www.meilisearch.com/docs/resources/self_hosting/deployment/aws
Deploy Meilisearch on an AWS EC2 instance. Covers installation, server configuration, and securing your instance.
This tutorial will guide you through setting up a production-ready Meilisearch instance on Amazon Web Services (AWS) using an EC2 instance.
[Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=running-production-oss) is the recommended way to run Meilisearch in production environments.
## Prerequisites
* An AWS account
* An EC2 instance running Ubuntu 22.04 LTS or Amazon Linux 2023
* An SSH key pair to connect to that instance
* A security group allowing inbound traffic on ports 22 (SSH), 80 (HTTP), and 443 (HTTPS)
AWS has extensive documentation on [launching EC2 instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EC2_GetStarted.html) and [connecting via SSH](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AccessingInstancesLinux.html).
## Step 1: Install Meilisearch
Log into your EC2 instance via SSH and update the system packages:
```sh theme={null}
sudo apt update && sudo apt upgrade -y
sudo apt install curl -y
```
```sh theme={null}
sudo yum update -y
sudo yum install curl -y
```
Next, use `curl` to download and run the Meilisearch command-line installer:
```sh theme={null}
curl -L https://install.meilisearch.com | sh
```
Move the binary file into `/usr/local/bin` to make it accessible from anywhere:
```sh theme={null}
sudo mv ./meilisearch /usr/local/bin/
```
## Step 2: Create system user
Running applications as root exposes you to unnecessary security risks. Create a dedicated user for Meilisearch:
```sh theme={null}
sudo useradd -d /var/lib/meilisearch -s /bin/false -m -r meilisearch
```
Give the new user ownership of the Meilisearch binary:
```sh theme={null}
sudo chown meilisearch:meilisearch /usr/local/bin/meilisearch
```
## Step 3: Create a configuration file
Create the directories where Meilisearch will store its data:
```bash theme={null}
sudo mkdir -p /var/lib/meilisearch/data /var/lib/meilisearch/dumps /var/lib/meilisearch/snapshots
sudo chown -R meilisearch:meilisearch /var/lib/meilisearch
sudo chmod 750 /var/lib/meilisearch
```
For production workloads, consider using an EBS volume for data storage. This allows for easy snapshots and volume resizing.
Download the default configuration file:
```bash theme={null}
curl https://raw.githubusercontent.com/meilisearch/meilisearch/latest/config.toml | sudo tee /etc/meilisearch.toml > /dev/null
```
Edit `/etc/meilisearch.toml` and update the following lines, replacing `MASTER_KEY` with a secure 16-byte string:
```ini theme={null}
env = "production"
master_key = "MASTER_KEY"
db_path = "/var/lib/meilisearch/data"
dump_dir = "/var/lib/meilisearch/dumps"
snapshot_dir = "/var/lib/meilisearch/snapshots"
```
Remember to choose a [safe master key](/docs/resources/self_hosting/security/basic_security#creating-the-master-key-in-a-self-hosted-instance).
## Step 4: Run Meilisearch as a service
Create a systemd service file to run Meilisearch as a background service:
```bash theme={null}
sudo cat << EOF > /etc/systemd/system/meilisearch.service
[Unit]
Description=Meilisearch
After=systemd-user-sessions.service
[Service]
Type=simple
WorkingDirectory=/var/lib/meilisearch
ExecStart=/usr/local/bin/meilisearch --config-file-path /etc/meilisearch.toml
User=meilisearch
Group=meilisearch
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
```
Reload systemd, then enable and start the service:
```bash theme={null}
sudo systemctl daemon-reload
sudo systemctl enable meilisearch
sudo systemctl start meilisearch
```
Verify the service is running:
```sh theme={null}
sudo systemctl status meilisearch
```
You should see a message confirming your service is active and running.
## Step 5: Secure and finish your setup
### 5.1. Configure security groups
Ensure your EC2 security group allows:
* Port 22 for SSH access
* Port 80 for HTTP traffic
* Port 443 for HTTPS traffic
You can configure this in the AWS Console under EC2 > Security Groups.
### 5.2. Set up a reverse proxy with Nginx
Install Nginx:
```bash theme={null}
sudo apt install nginx -y
```
```bash theme={null}
sudo yum install nginx -y
```
Remove the default configuration and create a new one for Meilisearch:
```bash theme={null}
sudo rm -f /etc/nginx/sites-enabled/default 2>/dev/null || true
sudo tee /etc/nginx/conf.d/meilisearch.conf > /dev/null << EOF
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name your_domain;
location / {
proxy_pass http://localhost:7700;
}
}
EOF
```
Replace `your_domain` with your actual domain name (or use `_` as a catch-all if you don't have one yet).
Enable and restart Nginx:
```bash theme={null}
sudo systemctl enable nginx
sudo systemctl restart nginx
```
### 5.3. Enable HTTPS with Let's Encrypt
Before enabling HTTPS, ensure you have a domain name pointing to your EC2 instance's public IP address.
Install certbot:
```bash theme={null}
sudo apt install certbot python3-certbot-nginx -y
```
```bash theme={null}
sudo yum install certbot python3-certbot-nginx -y
```
Run certbot to obtain and configure your SSL certificate:
```bash theme={null}
sudo certbot --nginx
```
Follow the prompts to enter your email, agree to the Terms of Service, and select your domain. Choose to redirect HTTP traffic to HTTPS when prompted.
Verify automatic renewal is configured:
```bash theme={null}
sudo certbot renew --dry-run
```
## Conclusion
Your Meilisearch instance is now running on AWS EC2 with:
* A dedicated system user for security
* Automatic restart via systemd
* Nginx reverse proxy
* HTTPS encryption via Let's Encrypt
For high-availability setups, consider using an Application Load Balancer (ALB) in front of multiple EC2 instances.
# Deploy on DigitalOcean
Source: https://www.meilisearch.com/docs/resources/self_hosting/deployment/digitalocean
Deploy Meilisearch on a DigitalOcean droplet. Covers installation, server configuration, and securing your instance.
This tutorial will guide you through setting up a production-ready Meilisearch instance. These instructions use a DigitalOcean droplet running Debian, but should be compatible with any hosting service running a Linux distro.
[Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=running-production-oss) is the recommended way to run Meilisearch in production environments.
## Prerequisites
* A DigitalOcean droplet running Debian 12
* An SSH key pair to connect to that machine
DigitalOcean has extensive documentation on [how to use SSH to connect to a droplet](https://www.digitalocean.com/docs/droplets/how-to/connect-with-ssh/).
## Step 1: Install Meilisearch
Log into your server via SSH, update the list of available packages, and install `curl`:
```sh theme={null}
apt update
apt install curl -y
```
Using the latest version of a package is good security practice, especially in production environments.
Next, use `curl` to download and run the Meilisearch command-line installer:
```sh theme={null}
# Install Meilisearch latest version from the script
curl -L https://install.meilisearch.com | sh
```
The Meilisearch installer is a set of scripts that ensure you will get the correct binary for your system.
Next, you need to make the binary accessible from anywhere in your system. Move the binary file into `/usr/local/bin`:
```sh theme={null}
mv ./meilisearch /usr/local/bin/
```
Meilisearch is now installed in your system, but it is not publicly accessible.
## Step 2: Create system user
Running applications as root exposes you to unnecessary security risks. To prevent that, create a dedicated user for Meilisearch:
```sh theme={null}
useradd -d /var/lib/meilisearch -s /bin/false -m -r meilisearch
```
Then give the new user ownership of the Meilisearch binary:
```sh theme={null}
chown meilisearch:meilisearch /usr/local/bin/meilisearch
```
## Step 3: Create a configuration file
After installing Meilisearch and taking the first step towards keeping your data safe, you need to set up a basic configuration file.
First, create the directories where Meilisearch will store its data:
```bash theme={null}
mkdir /var/lib/meilisearch/data /var/lib/meilisearch/dumps /var/lib/meilisearch/snapshots
chown -R meilisearch:meilisearch /var/lib/meilisearch
chmod 750 /var/lib/meilisearch
```
In this tutorial, you're creating the directories in your droplet's local disk. If you are using additional block storage, create these directories there.
Next, download the default configuration to `/etc`:
```bash theme={null}
curl https://raw.githubusercontent.com/meilisearch/meilisearch/latest/config.toml > /etc/meilisearch.toml
```
Finally, update the following lines in the `meilisearch.toml` file so Meilisearch uses the directories you created earlier to store its data, replacing `MASTER_KEY` with a 16-byte string:
```ini theme={null}
env = "production"
master_key = "MASTER_KEY"
db_path = "/var/lib/meilisearch/data"
dump_dir = "/var/lib/meilisearch/dumps"
snapshot_dir = "/var/lib/meilisearch/snapshots"
```
Remember to choose a [safe master key](/docs/resources/self_hosting/security/basic_security#creating-the-master-key-in-a-self-hosted-instance) and avoid exposing it in publicly accessible locations.
You have now configured your Meilisearch instance.
## Step 4: Run Meilisearch as a service
In Linux environments, a service is a process that can be launched when the operating system is booting and which will keep running in the background. If your program stops running for any reason, Linux will immediately restart the service, helping reduce downtime.
### 4.1. Create a service file
Service files are text files that tell your operating system how to run your program.
Run this command to create a service file in `/etc/systemd/system`:
```bash theme={null}
cat << EOF > /etc/systemd/system/meilisearch.service
[Unit]
Description=Meilisearch
After=systemd-user-sessions.service
[Service]
Type=simple
WorkingDirectory=/var/lib/meilisearch
ExecStart=/usr/local/bin/meilisearch --config-file-path /etc/meilisearch.toml
User=meilisearch
Group=meilisearch
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
```
### 4.2. Enable and start service
With your service file now ready to go, activate the service using `systemctl`:
```bash theme={null}
systemctl daemon-reload
systemctl enable meilisearch
systemctl start meilisearch
```
With `systemctl enable`, you're telling the operating system you want it to run at every boot. `systemctl start` then immediately starts the Meilisearch service.
Ensure everything is working by checking the service status:
```sh theme={null}
systemctl status meilisearch
```
You should see a message confirming your service is running:
```sh theme={null}
● meilisearch.service - Meilisearch
Loaded: loaded (/etc/systemd/system/meilisearch.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2023-04-10 14:27:49 UTC; 1min 8s ago
Main PID: 14960 (meilisearch)
```
## Step 5: Secure and finish your setup
At this point, Meilisearch is installed and running. It is also protected from eventual crashes and system restarts.
The next step is to make your instance publicly accessible.
If all the requests you send to Meilisearch are done by another application living in the same machine, you can safely skip this section.
### 5.1. Creating a reverse proxy with Nginx
A [reverse proxy](https://www.keycdn.com/support/nginx-reverse-proxy) is an application that will handle every communication between the outside world and your application. In this tutorial, you will use [Nginx](https://www.nginx.com/) as your reverse proxy to receive external HTTP requests and redirect them to Meilisearch.
First, install Nginx on your machine:
```bash theme={null}
apt-get install nginx -y
```
Next, delete the default configuration file:
```bash theme={null}
rm -f /etc/nginx/sites-enabled/default
```
Nginx comes with a set of default settings, such as its default HTTP port, that might conflict with Meilisearch.
Create a new configuration file specifying the reverse proxy settings. Replace `your_domain` with your actual domain name (or use `_` as a catch-all if you don't have one yet):
```sh theme={null}
cat << EOF > /etc/nginx/sites-enabled/meilisearch
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name your_domain;
location / {
proxy_pass http://localhost:7700;
}
}
EOF
```
Finally, enable the Nginx service:
```bash theme={null}
systemctl daemon-reload
systemctl enable nginx
systemctl restart nginx
```
Your Meilisearch instance is now publicly available.
### 5.2. Enable HTTPS
The only remaining problem is that Meilisearch processes requests via HTTP without any additional security. This is a major security flaw that could result in an attacker accessing your data.
This tutorial assumes you have a registered domain name, and you have correctly configured its DNS's `A record` to point to your DigitalOcean droplet's IP address. Consult the [DigitalOcean DNS documentation](https://docs.digitalocean.com/products/networking/dns/getting-started/dns-registrars/) for more information.
Use [certbot](https://certbot.eff.org/) to enable HTTPS on your server.
First, install the required packages on your system:
```bash theme={null}
sudo apt install certbot python3-certbot-nginx -y
```
Next, run certbot:
```bash theme={null}
certbot --nginx
```
Enter your email address, agree to the Terms and Conditions, and choose your domain. When prompted if you want to automatically redirect HTTP traffic, choose option `2: Redirect`.
Certbot will finish configuring Nginx. Once it is done, all traffic to your server will use HTTPS and you will have finished securing your Meilisearch instance.
Your security certificate must be renewed every 90 days. Certbot schedules the renewal automatically. Run a test to verify this process is in place:
```bash theme={null}
sudo certbot renew --dry-run
```
If this command returns no errors, you have successfully enabled HTTPS in your Nginx server.
## Conclusion
You have followed the main steps to provide a safe and stable service. Your Meilisearch instance is now up and running in a safe and publicly accessible environment thanks to the combination of a reverse proxy, HTTPS, and Meilisearch's built-in security keys.
# Deploy on Google Cloud
Source: https://www.meilisearch.com/docs/resources/self_hosting/deployment/gcp
Deploy Meilisearch on a Google Cloud Compute Engine VM. Covers installation, server configuration, and securing your instance.
This tutorial will guide you through setting up a production-ready Meilisearch instance on Google Cloud Platform (GCP) using a Compute Engine virtual machine.
[Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=running-production-oss) is the recommended way to run Meilisearch in production environments.
## Prerequisites
* A Google Cloud account with billing enabled
* A Compute Engine VM running Debian 12 or Ubuntu 22.04 LTS
* An SSH key pair or access via Google Cloud Console SSH
* Firewall rules allowing inbound traffic on ports 22 (SSH), 80 (HTTP), and 443 (HTTPS)
Google Cloud has extensive documentation on [creating VM instances](https://cloud.google.com/compute/docs/instances/create-start-instance) and [connecting via SSH](https://cloud.google.com/compute/docs/instances/connecting-to-instance).
## Step 1: Install Meilisearch
Connect to your VM via SSH (using the Google Cloud Console or gcloud CLI) and update the system:
```sh theme={null}
sudo apt update && sudo apt upgrade -y
sudo apt install curl -y
```
Download and run the Meilisearch installer:
```sh theme={null}
curl -L https://install.meilisearch.com | sh
```
Move the binary to make it accessible system-wide:
```sh theme={null}
sudo mv ./meilisearch /usr/local/bin/
```
## Step 2: Create system user
Create a dedicated user for running Meilisearch:
```sh theme={null}
sudo useradd -d /var/lib/meilisearch -s /bin/false -m -r meilisearch
```
Give the new user ownership of the Meilisearch binary:
```sh theme={null}
sudo chown meilisearch:meilisearch /usr/local/bin/meilisearch
```
## Step 3: Create a configuration file
Create data directories for Meilisearch:
```bash theme={null}
sudo mkdir -p /var/lib/meilisearch/data /var/lib/meilisearch/dumps /var/lib/meilisearch/snapshots
sudo chown -R meilisearch:meilisearch /var/lib/meilisearch
sudo chmod 750 /var/lib/meilisearch
```
For production workloads, consider attaching a persistent disk for data storage. This allows for easy snapshots and disk resizing independent of the VM.
Download the default configuration file:
```bash theme={null}
curl https://raw.githubusercontent.com/meilisearch/meilisearch/latest/config.toml | sudo tee /etc/meilisearch.toml > /dev/null
```
Edit `/etc/meilisearch.toml` and update these settings, replacing `MASTER_KEY` with a secure 16-byte string:
```ini theme={null}
env = "production"
master_key = "MASTER_KEY"
db_path = "/var/lib/meilisearch/data"
dump_dir = "/var/lib/meilisearch/dumps"
snapshot_dir = "/var/lib/meilisearch/snapshots"
```
Remember to choose a [safe master key](/docs/resources/self_hosting/security/basic_security#creating-the-master-key-in-a-self-hosted-instance).
## Step 4: Run Meilisearch as a service
Create a systemd service file:
```bash theme={null}
sudo tee /etc/systemd/system/meilisearch.service > /dev/null << EOF
[Unit]
Description=Meilisearch
After=systemd-user-sessions.service
[Service]
Type=simple
WorkingDirectory=/var/lib/meilisearch
ExecStart=/usr/local/bin/meilisearch --config-file-path /etc/meilisearch.toml
User=meilisearch
Group=meilisearch
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
```
Reload systemd, then enable and start the service:
```bash theme={null}
sudo systemctl daemon-reload
sudo systemctl enable meilisearch
sudo systemctl start meilisearch
```
Verify the service is running:
```sh theme={null}
sudo systemctl status meilisearch
```
## Step 5: Secure and finish your setup
### 5.1. Configure firewall rules
In the Google Cloud Console, navigate to VPC Network > Firewall and ensure you have rules allowing:
* Port 22 for SSH access
* Port 80 for HTTP traffic
* Port 443 for HTTPS traffic
You can also use gcloud CLI:
```bash theme={null}
gcloud compute firewall-rules create allow-http --allow tcp:80
gcloud compute firewall-rules create allow-https --allow tcp:443
```
### 5.2. Set up a reverse proxy with Nginx
Install Nginx:
```bash theme={null}
sudo apt install nginx -y
```
Remove the default configuration and create one for Meilisearch:
```bash theme={null}
sudo rm -f /etc/nginx/sites-enabled/default
sudo tee /etc/nginx/sites-enabled/meilisearch > /dev/null << EOF
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name your_domain;
location / {
proxy_pass http://localhost:7700;
}
}
EOF
```
Replace `your_domain` with your actual domain name (or use `_` as a catch-all if you don't have one yet).
Restart Nginx:
```bash theme={null}
sudo systemctl enable nginx
sudo systemctl restart nginx
```
### 5.3. Enable HTTPS with Let's Encrypt
Before enabling HTTPS, ensure you have a domain name pointing to your VM's external IP address. You can reserve a static IP in Google Cloud Console under VPC Network > External IP addresses.
Install certbot:
```bash theme={null}
sudo apt install certbot python3-certbot-nginx -y
```
Run certbot to obtain and configure your SSL certificate:
```bash theme={null}
sudo certbot --nginx
```
Follow the prompts to enter your email, agree to the Terms of Service, and select your domain. Choose to redirect HTTP traffic to HTTPS when prompted.
Verify automatic renewal is configured:
```bash theme={null}
sudo certbot renew --dry-run
```
## Conclusion
Your Meilisearch instance is now running on Google Cloud with:
* A dedicated system user for security
* Automatic restart via systemd
* Nginx reverse proxy
* HTTPS encryption via Let's Encrypt
For high-availability setups, consider using a managed instance group with a Cloud Load Balancer.
# Deploying Meilisearch
Source: https://www.meilisearch.com/docs/resources/self_hosting/deployment/overview
Choose a deployment strategy for running Meilisearch in production, from single-server setups to cloud provider deployments.
This section covers deploying Meilisearch to production environments. Each guide walks you through server setup, Meilisearch installation, systemd configuration, reverse proxy setup, and HTTPS.
[Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=deployment-overview) is the recommended way to run Meilisearch in production environments. It handles provisioning, updates, backups, and scaling automatically.
## Choosing a deployment target
| Target | Best for |
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| [Generic Linux server](/docs/resources/self_hosting/deployment/running_production) | Any Linux server or VPS. Start here if your provider isn't listed below. |
| [AWS](/docs/resources/self_hosting/deployment/aws) | Amazon Web Services EC2 instances |
| [GCP](/docs/resources/self_hosting/deployment/gcp) | Google Cloud Compute Engine VMs |
| [DigitalOcean](/docs/resources/self_hosting/deployment/digitalocean) | DigitalOcean droplets |
All cloud provider guides follow the same pattern: provision a server, install Meilisearch, configure systemd, set up Nginx with HTTPS, and configure firewall rules. The main differences are provider-specific networking and firewall configuration.
## Production checklist
Before going to production, make sure you have:
* [ ] Set a strong [master key](/docs/resources/self_hosting/security/master_api_keys) (at least 16 bytes)
* [ ] Set [environment to `production`](/docs/resources/self_hosting/configuration/reference#environment)
* [ ] Configured a reverse proxy (Nginx or Caddy) with HTTPS
* [ ] Set up [scheduled snapshots](/docs/resources/self_hosting/data_backup/snapshots) or a backup strategy
* [ ] Configured systemd to restart Meilisearch on failure
* [ ] Reviewed [RAM and threading settings](/docs/resources/self_hosting/performance/ram_multithreading) for your workload
# Running Meilisearch in production
Source: https://www.meilisearch.com/docs/resources/self_hosting/deployment/running_production
Deploy Meilisearch in a Digital Ocean droplet. Covers installation, server configuration, and securing your instance.
This tutorial will guide you through setting up a production-ready Meilisearch instance. These instructions use a DigitalOcean droplet running Debian, but should be compatible with any hosting service running a Linux distro.
[Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=running-production-oss) is the recommended way to run Meilisearch in production environments.
## Requirements
* A DigitalOcean droplet running Debian 12
* An SSH key pair to connect to that machine
DigitalOcean has extensive documentation on [how to use SSH to connect to a droplet](https://www.digitalocean.com/docs/droplets/how-to/connect-with-ssh/).
## Step 1: Install Meilisearch
Log into your server via SSH, update the list of available packages, and install `curl`:
```sh theme={null}
apt update
apt install curl -y
```
Using the latest version of a package is good security practice, especially in production environments.
Next, use `curl` to download and run the Meilisearch command-line installer:
```sh theme={null}
# Install Meilisearch latest version from the script
curl -L https://install.meilisearch.com | sh
```
The Meilisearch installer is a set of scripts that ensure you will get the correct binary for your system.
Next, you need to make the binary accessible from anywhere in your system. Move the binary file into `/usr/local/bin`:
```sh theme={null}
mv ./meilisearch /usr/local/bin/
```
Meilisearch is now installed in your system, but it is not publicly accessible.
## Step 2: Create system user
Running applications as root exposes you to unnecessary security risks. To prevent that, create a dedicated user for Meilisearch:
```sh theme={null}
useradd -d /var/lib/meilisearch -s /bin/false -m -r meilisearch
```
Then give the new user ownership of the Meilisearch binary:
```sh theme={null}
chown meilisearch:meilisearch /usr/local/bin/meilisearch
```
## Step 3: Create a configuration file
After installing Meilisearch and taking the first step towards keeping your data safe, you need to set up a basic configuration file.
First, create the directories where Meilisearch will store its data:
```bash theme={null}
mkdir /var/lib/meilisearch/data /var/lib/meilisearch/dumps /var/lib/meilisearch/snapshots
chown -R meilisearch:meilisearch /var/lib/meilisearch
chmod 750 /var/lib/meilisearch
```
In this tutorial, you're creating the directories in your droplet's local disk. If you are using additional block storage, create these directories there.
Next, download the default configuration to `/etc`:
```bash theme={null}
curl https://raw.githubusercontent.com/meilisearch/meilisearch/latest/config.toml > /etc/meilisearch.toml
```
Finally, update the following lines in the `meilisearch.toml` file so Meilisearch uses the directories you created earlier to store its data, replacing `MASTER_KEY` with a 16-byte string:
```ini theme={null}
env = "production"
master_key = "MASTER_KEY"
db_path = "/var/lib/meilisearch/data"
dump_dir = "/var/lib/meilisearch/dumps"
snapshot_dir = "/var/lib/meilisearch/snapshots"
```
Remember to choose a [safe master key](/docs/resources/self_hosting/security/basic_security#creating-the-master-key-in-a-self-hosted-instance) and avoid exposing it in publicly accessible locations.
You have now configured your Meilisearch instance.
## Step 4: Run Meilisearch as a service
In Linux environments, a service is a process that can be launched when the operating system is booting and which will keep running in the background. If your program stops running for any reason, Linux will immediately restart the service, helping reduce downtime.
### 4.1. Create a service file
Service files are text files that tell your operating system how to run your program.
Run this command to create a service file in `/etc/systemd/system`:
```bash theme={null}
cat << EOF > /etc/systemd/system/meilisearch.service
[Unit]
Description=Meilisearch
After=systemd-user-sessions.service
[Service]
Type=simple
WorkingDirectory=/var/lib/meilisearch
ExecStart=/usr/local/bin/meilisearch --config-file-path /etc/meilisearch.toml
User=meilisearch
Group=meilisearch
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
```
### 4.2. Enable and start service
With your service file now ready to go, activate the service using `systemctl`:
```bash theme={null}
systemctl enable meilisearch
systemctl start meilisearch
```
With `systemctl enable`, you're telling the operating system you want it to run at every boot. `systemctl start` then immediately starts the Meilisearch service.
Ensure everything is working by checking the service status:
```sh theme={null}
systemctl status meilisearch
```
You should see a message confirming your service is running:
```sh theme={null}
● meilisearch.service - Meilisearch
Loaded: loaded (/etc/systemd/system/meilisearch.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2023-04-10 14:27:49 UTC; 1min 8s ago
Main PID: 14960 (meilisearch)
```
## Step 5: Secure and finish your setup
At this point, Meilisearch is installed and running. It is also protected from eventual crashes and system restarts.
The next step is to make your instance publicly accessible.
If all the requests you send to Meilisearch are done by another application living in the same machine, you can safely skip this section.
### 5.1. Creating a reverse proxy with Nginx
A [reverse proxy](https://www.keycdn.com/support/nginx-reverse-proxy) is an application that will handle every communication between the outside world and your application. In this tutorial, you will use [Nginx](https://www.nginx.com/) as your reverse proxy to receive external HTTP requests and redirect them to Meilisearch.
First, install Nginx on your machine:
```bash theme={null}
apt-get install nginx -y
```
Next, delete the default configuration file:
```bash theme={null}
rm -f /etc/nginx/sites-enabled/default
```
Nginx comes with a set of default settings, such as its default HTTP port, that might conflict with Meilisearch.
Create a new configuration file specifying the reverse proxy settings:
```sh theme={null}
cat << EOF > /etc/nginx/sites-enabled/meilisearch
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
location / {
proxy_pass http://localhost:7700;
}
}
EOF
```
Finally, enable the Nginx service:
```bash theme={null}
systemctl daemon-reload
systemctl enable nginx
systemctl restart nginx
```
Your Meilisearch instance is now publicly available.
### 5.2. Enable HTTPS
The only remaining problem is that Meilisearch processes requests via HTTP without any additional security. This is a major security flaw that could result in an attacker accessing your data.
This tutorial assumes you have a registered domain name, and you have correctly configured its DNS's `A record` to point to your DigitalOcean droplet's IP address. Consult the [DigitalOcean DNS documentation](https://docs.digitalocean.com/products/networking/dns/getting-started/dns-registrars/) for more information.
Use [certbot](https://certbot.eff.org/) to configure enable HTTPS in your server.
First, install the required packages on your system:
```bash theme={null}
sudo apt install certbot python3-certbot-nginx -y
```
Next, run certbot:
```bash theme={null}
certbot --nginx
```
Enter your email address, agree to the Terms and Conditions, and choose your domain. When prompted if you want to automatically redirect HTTP traffic, choose option `2: Redirect`.
Certbot will finish configuring Nginx. Once it is done, all traffic to your server will use HTTPS and you will have finished securing your Meilisearch instance.
Your security certificate must be renewed every 90 days. Certbot schedules the renewal automatically. Run a test to verify this process is in place:
```bash theme={null}
sudo certbot renew --dry-run
```
If this command returns no errors, you have successfully enabled HTTPS in your Nginx server.
## Conclusion
You have followed the main steps to provide a safe and stable service. Your Meilisearch instance is now up and running in a safe and publicly accessible environment thanks to the combination of a reverse proxy, HTTPS, and Meilisearch's built-in security keys.
# Using Meilisearch with Docker
Source: https://www.meilisearch.com/docs/resources/self_hosting/getting_started/docker
Learn how to use Docker to download and run Meilisearch, configure its behavior, and manage your Meilisearch data.
In this guide you will learn how to use Docker to download and run Meilisearch, configure its behavior, and manage your Meilisearch data.
Docker is a tool that bundles applications into containers. Docker containers ensure your application runs the same way in different environments. When using Docker for development, we recommend following [the official instructions to install Docker Desktop](https://docs.docker.com/get-docker/).
## Download Meilisearch with Docker
Docker containers are distributed in images. To use Meilisearch, use the `docker pull` command to download a Meilisearch image:
```sh theme={null}
docker pull getmeili/meilisearch:latest
```
Meilisearch deploys a new Docker image with every release of the engine. Each image is tagged with the corresponding Meilisearch version, indicated in the above example by the text following the `:` symbol. You can see [the full list of available Meilisearch Docker images](https://hub.docker.com/r/getmeili/meilisearch/tags#!) on Docker Hub.
The `latest` tag will always download the most recent Meilisearch release. Meilisearch advises against using it, as it might result in different machines running different images if significant time passes between setting up each one of them.
## Run Meilisearch with Docker
After completing the previous step, use `docker run` to launch the Meilisearch image:
```sh theme={null}
docker run -it --rm \
-p 7700:7700 \
-v $(pwd)/meili_data:/meili_data \
getmeili/meilisearch:latest
```
### Configure Meilisearch
Meilisearch accepts a number of instance options during launch. You can configure these in two ways: environment variables and CLI arguments. Note that some options are only available as CLI arguments. [Consult our configuration reference for more details](/docs/resources/self_hosting/configuration/overview).
#### Passing instance options with environment variables
To pass environment variables to Docker, add the `-e` argument to `docker run`. The example below launches Meilisearch with a master key:
```sh theme={null}
docker run -it --rm \
-p 7700:7700 \
-e MEILI_MASTER_KEY='MASTER_KEY'\
-v $(pwd)/meili_data:/meili_data \
getmeili/meilisearch:latest
```
#### Passing instance options with CLI arguments
If you want to pass command-line arguments to Meilisearch with Docker, you must add a line to the end of your `docker run` command explicitly launching the `meilisearch` binary:
```sh theme={null}
docker run -it --rm \
-p 7700:7700 \
-v $(pwd)/meili_data:/meili_data \
getmeili/meilisearch:latest \
meilisearch --master-key="MASTER_KEY"
```
## Managing data
When using Docker, your working directory is `/meili_data`. This means the location of your database file is `/meili_data/data.ms`.
### Data persistency
By default, data written to a Docker container is deleted every time the container stops running. This data includes your indexes and the documents they store.
To keep your data intact between reboots, specify a dedicated volume by running Docker with the `-v` command-line option:
```sh theme={null}
docker run -it --rm \
-p 7700:7700 \
-v $(pwd)/meili_data:/meili_data \
getmeili/meilisearch:latest
```
The example above uses `$(pwd)/meili_data`, which is a directory in the host machine. Depending on your OS, mounting volumes from the host to the container might result in performance loss and is only recommended when developing your application.
### Generating dumps and updating Meilisearch
To export a dump, [use the create dump endpoint as described in our dumps guide](/docs/resources/self_hosting/data_backup/dumps). Once the task is complete, you can access the dump file in `/meili_data/dumps` inside the volume you passed with `-v`.
To import a dump, use Meilisearch's `--import-dump` command-line option and specify the path to the dump file. Make sure the path points to a volume reachable by Docker:
```sh theme={null}
docker run -it --rm \
-p 7700:7700 \
-v $(pwd)/meili_data:/meili_data \
getmeili/meilisearch:latest \
meilisearch --import-dump /meili_data/dumps/20200813-042312213.dump
```
Note that exporting and importing dumps require using command-line arguments. [For more information on how to run Meilisearch with CLI options and Docker, refer to this guide's relevant section.](#passing-instance-options-with-cli-arguments)
If you are storing your data in a persistent volume as instructed in [the data persistency section](#data-persistency), you must delete `/meili_data/data.ms` in that volume before importing a dump.
Use dumps to migrate data between different Meilisearch releases. [Read more about updating Meilisearch in our dedicated guide.](/docs/resources/migration/updating)
### Snapshots
To generate a Meilisearch snapshot with Docker, launch Meilisearch with `--schedule-snapshot` and `--snapshot-dir`:
```sh theme={null}
docker run -it --rm \
-p 7700:7700 \
-v $(pwd)/meili_data:/meili_data \
getmeili/meilisearch:latest \
meilisearch --schedule-snapshot --snapshot-dir /meili_data/snapshots
```
`--snapshot-dir` should point to a folder inside the Docker working directory for Meilisearch, `/meili_data`. Once generated, snapshots will be available in the configured directory.
To import a snapshot, launch Meilisearch with the `--import-snapshot` option:
```sh theme={null}
docker run -it --rm \
-p 7700:7700 \
-v $(pwd)/meili_data:/meili_data \
getmeili/meilisearch:latest \
meilisearch --import-snapshot /meili_data/snapshots/data.ms.snapshot
```
Use snapshots for backup or when migrating data between two Meilisearch instances of the same version. [Read more about snapshots in our guide.](/docs/resources/self_hosting/data_backup/snapshots)
# Install Meilisearch locally
Source: https://www.meilisearch.com/docs/resources/self_hosting/getting_started/install_locally
Install Meilisearch locally on Linux, macOS, or Windows using cURL, Docker, Homebrew, APT, or from source.
## Supported operating systems
Meilisearch officially supports the following operating systems. Binaries might work on other environments without official support.
| OS | Requirements |
| ----------- | -------------------------------------------------------------------------------------- |
| **Linux** | `amd64/x86_64` or `aarch64/arm64` with glibc 2.35+. Check with `ldd --version`. |
| **macOS** | macOS 14 Sonoma or later, `amd64` or `arm64`. |
| **Windows** | Windows Server 2022 or later. Windows OS 10+ may work but is not officially supported. |
Use [Meilisearch Cloud](https://www.meilisearch.com/cloud) to integrate Meilisearch with applications hosted in unsupported operating systems.
## Meilisearch Cloud
[Meilisearch Cloud](https://www.meilisearch.com/cloud) simplifies installing, maintaining, and updating Meilisearch. [Get started with a 14-day free trial](https://cloud.meilisearch.com/register).
Take a look at the [Meilisearch Cloud tutorial](/docs/getting_started/first_project) for more information on setting up and using Meilisearch's cloud service.
## Local installation
Download the **latest stable release** of Meilisearch with **cURL**.
Launch Meilisearch to start the server.
```bash theme={null}
# Install Meilisearch
curl -L https://install.meilisearch.com | sh
# Launch Meilisearch
./meilisearch
```
Download the **latest stable release** of Meilisearch with **[Homebrew](https://brew.sh/)**, a package manager for MacOS.
Launch Meilisearch to start the server.
```bash theme={null}
# Update brew and install Meilisearch
brew update && brew install meilisearch
# Launch Meilisearch
meilisearch
```
When using **Docker**, you can run [any tag available in our official Docker image](https://hub.docker.com/r/getmeili/meilisearch/tags).
These commands launch the **latest stable release** of Meilisearch.
```bash theme={null}
# Fetch the latest version of Meilisearch image from DockerHub
docker pull getmeili/meilisearch:v1.37
# Launch Meilisearch in development mode with a master key
docker run -it --rm \
-p 7700:7700 \
-e MEILI_ENV='development' \
-v $(pwd)/meili_data:/meili_data \
getmeili/meilisearch:v1.37
# Use ${pwd} instead of $(pwd) in PowerShell
```
You can learn more about [using Meilisearch with Docker in our dedicated guide](/docs/resources/self_hosting/getting_started/docker).
Download the **latest stable release** of Meilisearch with **APT**.
Launch Meilisearch to start the server.
```bash theme={null}
# Add Meilisearch package
echo "deb [trusted=yes] https://apt.fury.io/meilisearch/ /" | sudo tee /etc/apt/sources.list.d/fury.list
# Update APT and install Meilisearch
sudo apt update && sudo apt install meilisearch
# Launch Meilisearch
meilisearch
```
Meilisearch is written in Rust. To compile it, [install the Rust toolchain](https://www.rust-lang.org/tools/install).
Once the Rust toolchain is installed, clone the repository on your local system and change it to your working directory.
```bash theme={null}
git clone https://github.com/meilisearch/meilisearch
cd meilisearch
```
Choose the release you want to use. You can find the full list [here](https://github.com/meilisearch/meilisearch/releases).
In the cloned repository, run the following command to access the most recent version of Meilisearch:
```bash theme={null}
git checkout latest
```
Finally, update the Rust toolchain, compile the project, and execute the binary.
```bash theme={null}
# Update the Rust toolchain to the latest version
rustup update
# Compile the project
cargo build --release
# Execute the binary
./target/release/meilisearch
```
To install Meilisearch on Windows, you can:
* Use Docker (see "Docker" tab above)
* Download the latest binary (see "Direct download" tab above)
* Use the installation script (see "cURL" tab above) if you have installed [Cygwin](https://www.cygwin.com/), [WSL](https://learn.microsoft.com/en-us/windows/wsl/), or equivalent
* Compile from source (see "Source" tab above)
To learn more about the Windows command prompt, follow this [introductory guide](https://www.makeuseof.com/tag/a-beginners-guide-to-the-windows-command-line/).
If none of the other installation options work for you, you can always download the Meilisearch binary directly on GitHub.
Go to the [latest Meilisearch release](https://github.com/meilisearch/meilisearch/releases/latest), scroll down to "Assets", and select the binary corresponding to your operating system.
```bash theme={null}
# Rename binary to meilisearch. Replace {meilisearch_os} with the name of the downloaded binary
mv {meilisearch_os} meilisearch
# Give the binary execute permission
chmod +x meilisearch
# Launch Meilisearch
./meilisearch
```
## Installing older versions of Meilisearch
We discourage the use of older Meilisearch versions. Before installing an older version, please [contact support](https://discord.meilisearch.com) to check if the latest version might work as well.
Download the binary of a specific version under "Assets" on our [GitHub changelog](https://github.com/meilisearch/meilisearch/releases).
```bash theme={null}
# Replace {meilisearch_version} and {meilisearch_os} with the specific version and OS you want to download
# For example, if you want to download v1.0 on macOS,
# replace {meilisearch_version} and {meilisearch_os} with v1.0 and meilisearch-macos-amd64 respectively
curl -OL https://github.com/meilisearch/meilisearch/releases/download/{meilisearch_version}/{meilisearch_os}
# Rename binary to meilisearch. Replace {meilisearch_os} with the name of the downloaded binary
mv {meilisearch_os} meilisearch
# Give the binary execute permission
chmod +x meilisearch
# Launch Meilisearch
./meilisearch
```
When using **Docker**, you can run [any tag available in our official Docker image](https://hub.docker.com/r/getmeili/meilisearch/tags).
```bash theme={null}
# Fetch specific version of Meilisearch image from DockerHub. Replace vX.Y.Z with the version you want to use
docker pull getmeili/meilisearch:vX.Y.Z
# Launch Meilisearch in development mode with a master key
docker run -it --rm \
-p 7700:7700 \
-e MEILI_ENV='development' \
-v $(pwd)/meili_data:/meili_data \
getmeili/meilisearch:vX.Y.Z
# Use ${pwd} instead of $(pwd) in PowerShell
```
Learn more about [using Meilisearch with Docker in our dedicated guide](/docs/resources/self_hosting/getting_started/docker).
Meilisearch is written in Rust. To compile it, [install the Rust toolchain](https://www.rust-lang.org/tools/install).
Once the Rust toolchain is installed, clone the repository on your local system and change it to your working directory.
```bash theme={null}
git clone https://github.com/meilisearch/meilisearch
cd meilisearch
```
Choose the release you want to use. You can find the full list [here](https://github.com/meilisearch/meilisearch/releases).
In the cloned repository, run the following command to access a specific version of Meilisearch:
```bash theme={null}
# Replace vX.Y.Z with the specific version you want to use
git checkout vX.Y.Z
```
Finally, update the Rust toolchain, compile the project, and execute the binary.
```bash theme={null}
# Update the Rust toolchain to the latest version
rustup update
# Compile the project
cargo build --release
# Execute the binary
./target/release/meilisearch
```
Download the binary of a specific version under "Assets" on our [GitHub changelog](https://github.com/meilisearch/meilisearch/releases).
```bash theme={null}
# Rename binary to meilisearch. Replace {meilisearch_os} with the name of the downloaded binary
mv {meilisearch_os} meilisearch
# Give the binary execute permission
chmod +x meilisearch
# Launch Meilisearch
./meilisearch
```
## Troubleshooting
If the provided [binaries](https://github.com/meilisearch/meilisearch/releases) do not work on your operating system, try [building Meilisearch from source](#local-installation). If compilation fails, Meilisearch is not compatible with your machine.
# Getting started with self-hosted Meilisearch
Source: https://www.meilisearch.com/docs/resources/self_hosting/getting_started/quick_start
Learn how to install Meilisearch, index a dataset, and perform your first search.
This quick start walks you through installing Meilisearch, adding documents, and performing your first search.
To follow this tutorial you need:
* A [command line terminal](https://www.learnenough.com/command-line-tutorial#sec-running_a_terminal)
* [cURL](https://curl.se)
Using Meilisearch Cloud? Check out the dedicated guide, [Getting started with Meilisearch Cloud](/docs/getting_started/first_project).
## Setup and installation
First, you need to download and install Meilisearch. This command installs the latest Meilisearch version in your local machine:
```bash theme={null}
# Install Meilisearch
curl -L https://install.meilisearch.com | sh
```
The rest of this guide assumes you are using Meilisearch locally, but you may also use Meilisearch over a cloud service such as [Meilisearch Cloud](https://www.meilisearch.com/cloud).
Learn more about other installation options in the [installation guide](/docs/resources/self_hosting/getting_started/install_locally).
### Running Meilisearch
Next, launch Meilisearch by running the following command in your terminal:
```bash theme={null}
# Launch Meilisearch
./meilisearch --master-key="aSampleMasterKey"
```
This tutorial uses `aSampleMasterKey` as a master key, but you may change it to any alphanumeric string with 16 or more bytes. In most cases, one character corresponds to one byte.
You should see something like this in response:
```
888b d888 d8b 888 d8b 888
8888b d8888 Y8P 888 Y8P 888
88888b.d88888 888 888
888Y88888P888 .d88b. 888 888 888 .d8888b .d88b. 8888b. 888d888 .d8888b 88888b.
888 Y888P 888 d8P Y8b 888 888 888 88K d8P Y8b "88b 888P" d88P" 888 "88b
888 Y8P 888 88888888 888 888 888 "Y8888b. 88888888 .d888888 888 888 888 888
888 " 888 Y8b. 888 888 888 X88 Y8b. 888 888 888 Y88b. 888 888
888 888 "Y8888 888 888 888 88888P' "Y8888 "Y888888 888 "Y8888P 888 888
Database path: "./data.ms"
Server listening on: "localhost:7700"
```
You now have a Meilisearch instance running in your terminal window. Keep this window open for the rest of this tutorial.
The above command uses the `--master-key` configuration option to secure Meilisearch. Setting a master key is optional but strongly recommended in development environments. Master keys are mandatory in production environments.
To learn more about securing Meilisearch, refer to the [security tutorial](/docs/resources/self_hosting/security/basic_security).
## Add documents
In this quick start, you will search through a collection of movies.
To follow along, first click this link to download the file: movies.json. Then, move the downloaded file into your working directory.
Meilisearch accepts data in JSON, NDJSON, and CSV formats.
Open a new terminal window and run the following command:
```bash cURL theme={null}
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/documents?primaryKey=id' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer aSampleMasterKey' \
--data-binary @movies.json
```
```javascript JS theme={null}
// With npm:
// npm install meilisearch
// Or with pnpm:
// pnpm add meilisearch
// In your .js file:
// With the `require` syntax:
const { MeiliSearch } = require('meilisearch')
const movies = require('./movies.json')
// With the `import` syntax:
import { MeiliSearch } from 'meilisearch'
import movies from './movies.json'
const client = new MeiliSearch({
host: 'MEILISEARCH_URL',
apiKey: 'aSampleMasterKey'
})
client.index('movies').addDocuments(movies)
.then((res) => console.log(res))
```
```python Python theme={null}
# In the command line:
# pip3 install meilisearch
# In your .py file:
import meilisearch
import json
client = meilisearch.Client('MEILISEARCH_URL', 'aSampleMasterKey')
json_file = open('movies.json', encoding='utf-8')
movies = json.load(json_file)
client.index('movies').add_documents(movies)
```
```php PHP theme={null}
/**
* Using `meilisearch-php` with the Guzzle HTTP client, in the command line:
* composer require meilisearch/meilisearch-php \
* guzzlehttp/guzzle \
* http-interop/http-factory-guzzle:^1.0
*/
/**
* In your PHP file:
*/
index('movies')->addDocuments($movies);
```
```java Java theme={null}
// For Maven:
// Add the following code to the `` section of your project:
//
//
// com.meilisearch.sdk
// meilisearch-java
// 0.21.0
// pom
//
// For Gradle
// Add the following line to the `dependencies` section of your `build.gradle`:
//
// implementation 'com.meilisearch.sdk:meilisearch-java:0.21.0'
// In your .java file:
import com.meilisearch.sdk;
import java.nio.file.Files;
import java.nio.file.Path;
Path fileName = Path.of("movies.json");
String moviesJson = Files.readString(fileName);
Client client = new Client(new Config("MEILISEARCH_URL", "aSampleMasterKey"));
Index index = client.index("movies");
index.addDocuments(moviesJson);
```
```ruby Ruby theme={null}
# In the command line:
# bundle add meilisearch
# In your .rb file:
require 'json'
require 'meilisearch'
client = MeiliSearch::Client.new('MEILISEARCH_URL', 'aSampleMasterKey')
movies_json = File.read('movies.json')
movies = JSON.parse(movies_json)
client.index('movies').add_documents(movies)
```
```go Go theme={null}
// In the command line:
// go get -u github.com/meilisearch/meilisearch-go
// In your .go file:
package main
import (
"os"
"encoding/json"
"io"
"github.com/meilisearch/meilisearch-go"
)
func main() {
client := meilisearch.New("MEILISEARCH_URL", meilisearch.WithAPIKey("masterKey"))
jsonFile, _ := os.Open("movies.json")
defer jsonFile.Close()
byteValue, _ := io.ReadAll(jsonFile)
var movies []map[string]interface{}
json.Unmarshal(byteValue, &movies)
_, err := client.Index("movies").AddDocuments(movies, nil)
if err != nil {
panic(err)
}
}
```
```csharp C# theme={null}
// In the command line:
// dotnet add package Meilisearch
// In your .cs file:
using System.IO;
using System.Text.Json;
using Meilisearch;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace Meilisearch_demo
{
public class Movie
{
public string Id { get; set; }
public string Title { get; set; }
public string Poster { get; set; }
public string Overview { get; set; }
public IEnumerable Genres { get; set; }
}
internal class Program
{
static async Task Main(string[] args)
{
MeilisearchClient client = new MeilisearchClient("MEILISEARCH_URL", "aSampleMasterKey");
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
string jsonString = await File.ReadAllTextAsync("movies.json");
var movies = JsonSerializer.Deserialize>(jsonString, options);
var index = client.Index("movies");
await index.AddDocumentsAsync(movies);
}
}
}
```
```text Rust theme={null}
// In your .toml file:
[dependencies]
meilisearch-sdk = "0.33.0"
# futures: because we want to block on futures
futures = "0.3"
# serde: required if you are going to use documents
serde = { version="1.0", features = ["derive"] }
# serde_json: required in some parts of this guide
serde_json = "1.0"
// In your .rs file:
// Documents in the Rust library are strongly typed
#[derive(Serialize, Deserialize)]
struct Movie {
id: i64,
title: String,
poster: String,
overview: String,
release_date: i64,
genres: Vec
}
// You will often need this `Movie` struct in other parts of this documentation. (you will have to change it a bit sometimes)
// You can also use schemaless values, by putting a `serde_json::Value` inside your own struct like this:
#[derive(Serialize, Deserialize)]
struct Movie {
id: i64,
#[serde(flatten)]
value: serde_json::Value,
}
// Then, add documents into the index:
use meilisearch_sdk::{
indexes::*,
client::*,
search::*,
settings::*
};
use serde::{Serialize, Deserialize};
use std::{io::prelude::*, fs::File};
use futures::executor::block_on;
fn main() { block_on(async move {
let client = Client::new("MEILISEARCH_URL", Some("aSampleMasterKey"));
// Reading and parsing the file
let mut file = File::open("movies.json")
.unwrap();
let mut content = String::new();
file
.read_to_string(&mut content)
.unwrap();
let movies_docs: Vec = serde_json::from_str(&content)
.unwrap();
// Adding documents
client
.index("movies")
.add_documents(&movies_docs, None)
.await
.unwrap();
})}
```
```swift Swift theme={null}
// Add this to your `Package.swift`
dependencies: [
.package(url: "https://github.com/meilisearch/meilisearch-swift.git", from: "0.17.0")
]
// In your .swift file:
let path = Bundle.main.url(forResource: "movies", withExtension: "json")!
let documents: Data = try Data(contentsOf: path)
let client = try MeiliSearch(host: "MEILISEARCH_URL", apiKey: "aSampleMasterKey")
client.index("movies").addDocuments(documents: documents) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart theme={null}
// In the command line:
// dart pub add meilisearch
// In your .dart file:
import 'package:meilisearch/meilisearch.dart';
import 'dart:io';
import 'dart:convert';
var client = MeiliSearchClient('MEILISEARCH_URL', 'aSampleMasterKey');
final json = await File('movies.json').readAsString();
await client.index('movies').addDocumentsJson(json);
```
Meilisearch stores data in the form of discrete records, called [documents](/docs/resources/internals/documents). Each document is an object composed of multiple fields, which are pairs of one attribute and one value:
```json theme={null}
{
"attribute": "value"
}
```
Documents are grouped into collections, called [indexes](/docs/resources/internals/indexes).
The previous command added documents from `movies.json` to a new index called `movies`. It also set `id` as the primary key.
Every index must have a [primary key](/docs/resources/internals/primary_key#primary-field), an attribute shared across all documents in that index. If you try adding documents to an index and even a single one is missing the primary key, none of the documents will be stored.
If you do not explicitly set the primary key, Meilisearch [infers](/docs/resources/internals/primary_key#meilisearch-guesses-your-primary-key) it from your dataset.
After adding documents, you should receive a response like this:
```json theme={null}
{
"taskUid": 0,
"indexUid": "movies",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
Use the returned `taskUid` to [check the status](/docs/reference/api/tasks/get-task) of your documents:
```bash cURL theme={null}
curl \
-X GET 'MEILISEARCH_URL/tasks/0' \
-H 'Authorization: Bearer aSampleMasterKey'
```
```javascript JS theme={null}
client.tasks.getTask(0)
```
```python Python theme={null}
client.get_task(0)
```
```php PHP theme={null}
$client->getTask(0);
```
```java Java theme={null}
client.getTask(0);
```
```ruby Ruby theme={null}
client.task(0)
```
```go Go theme={null}
client.GetTask(0)
```
```csharp C# theme={null}
TaskInfo task = await client.GetTaskAsync(0);
```
```rust Rust theme={null}
client
.get_task(0)
.await
.unwrap();
```
```swift Swift theme={null}
client.getTask(taskUid: 0) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart theme={null}
await client.getTask(0);
```
Most database operations in Meilisearch are [asynchronous](/docs/capabilities/indexing/tasks_and_batches/async_operations). Rather than being processed instantly, **API requests are added to a queue and processed one at a time**.
If the document addition is successful, the response should look like this:
```json theme={null}
{
"uid": 0,
"indexUid": "movies",
"status": "succeeded",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 19547,
"indexedDocuments": 19547
},
"error": null,
"duration": "PT0.030750S",
"enqueuedAt": "2021-12-20T12:39:18.349288Z",
"startedAt": "2021-12-20T12:39:18.352490Z",
"finishedAt": "2021-12-20T12:39:18.380038Z"
}
```
If `status` is `enqueued` or `processing`, all you have to do is wait a short time and check again. Proceed to the next step once the task `status` has changed to `succeeded`.
## Search
Now that you have Meilisearch set up, you can start searching!
```bash cURL theme={null}
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer aSampleMasterKey' \
--data-binary '{ "q": "botman" }'
```
```javascript JS theme={null}
client.index('movies').search('botman').then((res) => console.log(res))
```
```python Python theme={null}
client.index('movies').search('botman')
```
```php PHP theme={null}
$client->index('movies')->search('botman');
```
```java Java theme={null}
client.index("movies").search("botman");
```
```ruby Ruby theme={null}
client.index('movies').search('botman')
```
```go Go theme={null}
client.Index("movies").Search("botman", &meilisearch.SearchRequest{})
```
```csharp C# theme={null}
MeilisearchClient client = new MeilisearchClient("MEILISEARCH_URL", "masterKey");
var index = client.Index("movies");
var movies = await index.SearchAsync("botman");
foreach (var movie in movies.Hits)
{
Console.WriteLine(movie.Title);
}
```
```rust Rust theme={null}
// You can build a `SearchQuery` and execute it later:
let query: SearchQuery = SearchQuery::new(&movies)
.with_query("botman")
.build();
let results: SearchResults = client
.index("movies")
.execute_query(&query)
.await
.unwrap();
// You can build a `SearchQuery` and execute it directly:
let results: SearchResults = SearchQuery::new(&movies)
.with_query("botman")
.execute()
.await
.unwrap();
// You can search in an index directly:
let results: SearchResults = client
.index("movies")
.search()
.with_query("botman")
.execute()
.await
.unwrap();
```
```swift Swift theme={null}
client.index("movies").search(SearchParameters(query: "botman")) { (result) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart theme={null}
await client.index('movies').search('botman');
```
This tutorial queries Meilisearch with the master key. In production environments, this is a security risk. Prefer using API keys to access Meilisearch's API in any public-facing application.
In the above code sample, the parameter `q` represents the search query. This query instructs Meilisearch to search for `botman` in the documents you added in [the previous step](#add-documents):
```json theme={null}
{
"hits": [
{
"id": 29751,
"title": "Batman Unmasked: The Psychology of the Dark Knight",
"poster": "https://image.tmdb.org/t/p/w1280/jjHu128XLARc2k4cJrblAvZe0HE.jpg",
"overview": "Delve into the world of Batman and the vigilante justice tha",
"release_date": "2008-07-15"
},
{
"id": 471474,
"title": "Batman: Gotham by Gaslight",
"poster": "https://image.tmdb.org/t/p/w1280/7souLi5zqQCnpZVghaXv0Wowi0y.jpg",
"overview": "ve Victorian Age Gotham City, Batman begins his war on crime",
"release_date": "2018-01-12"
},
…
],
"estimatedTotalHits": 66,
"query": "botman",
"limit": 20,
"offset": 0,
"processingTimeMs": 12
}
```
By default, Meilisearch only returns the first 20 results for a search query. You can change this using the [`limit` parameter](/docs/reference/api/search/search-with-post#body-limit).
## What's next?
You now know how to install Meilisearch, create an index, add documents, check the status of an asynchronous task, and make a search request.
If you'd like to search through the documents you just added using a clean browser interface rather than the terminal, you can do so with [our built-in search preview](/docs/resources/self_hosting/getting_started/search_preview). You can also [learn how to quickly build a front-end interface](/docs/getting_started/instant_meilisearch/javascript) of your own.
For a more advanced approach, consult the [API reference](/docs/reference/api/requests).
# Search preview
Source: https://www.meilisearch.com/docs/resources/self_hosting/getting_started/search_preview
Meilisearch comes with a built-in search interface for quick testing during development.
Meilisearch Cloud gives you access to a dedicated search preview interface. This is useful to test search result relevancy when you are tweaking an index's settings.
If you are self-hosting Meilisearch and need a local search interface, access `http://localhost:7700` in your browser. This local preview only allows you to perform plain searches and offers no customization options.
## Accessing and using search preview
Log into your [Meilisearch Cloud](https://cloud.meilisearch.com/login) account, navigate to your project, then click on "Search preview":
Select the index you want to search on using the input on the left-hand side:
Then use the main input to perform plain keyword searches:
When debugging relevancy, you may want to activate the "Ranking score" option. This displays the overall [ranking score](/docs/capabilities/full_text_search/relevancy/ranking_score) for each result, together with the score for each individual ranking rule:
## Configuring search options
Use the menu on the left-hand side to configure [sorting](/docs/capabilities/filtering_sorting_faceting/how_to/sort_results) and [filtering](/docs/capabilities/filtering_sorting_faceting/getting_started). These require you to first edit your index's sortable and filterable attributes. You may additionally configure any filterable attributes as facets. In this example, "Genres" is one of the configured facets:
You can also perform [AI-powered searches](/docs/capabilities/hybrid_search/getting_started) if this functionality has been enabled for your project.
Clicking on "Advanced parameters" gives you access to further customization options, including setting which document fields Meilisearch returns and explicitly declaring the search language:
## Exporting search options
You can export the full search query for further testing in other tools and environments. Click on the cloud icon next to "Advanced parameters", then choose to download a JSON file or copy the query to your clipboard:
# Computing Hugging Face embeddings with the GPU
Source: https://www.meilisearch.com/docs/resources/self_hosting/huggingface_gpu
This guide for experienced users shows you how to compile a Meilisearch binary that generates Hugging Face embeddings with an Nvidia GPU.
This guide is aimed at experienced users working with a self-hosted Meilisearch instance. It shows you how to compile a Meilisearch binary that generates Hugging Face embeddings with an Nvidia GPU.
## Prerequisites
* A [CUDA-compatible Linux distribution](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#id12)
* An Nvidia GPU with CUDA support
* A modern Rust compiler
## Install CUDA
Follow Nvidia's [CUDA installation instructions](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html).
## Verify your CUDA install
After you have installed CUDA in your machine, run the following command in your command-line terminal:
```sh theme={null}
nvcc --version | head -1
```
If CUDA is working correctly, you will see the following response:
```
nvcc: NVIDIA (R) Cuda compiler driver
```
## Compile Meilisearch
First, clone Meilisearch:
```sh theme={null}
git clone https://github.com/meilisearch/meilisearch.git
```
Then, compile the Meilisearch binary with `cuda` enabled:
```sh theme={null}
cargo build --release --features cuda
```
This might take a few moments. Once the compiler is done, you should have a CUDA-compatible Meilisearch binary.
## Configure the Hugging Face embedder
Run your freshly compiled binary:
```sh theme={null}
./meilisearch
```
Then add the Hugging Face embedder to your index settings:
```sh theme={null}
curl \
-X PATCH 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/embedders' \
-H 'Content-Type: application/json' \
--data-binary '{ "default": { "source": "huggingFace" } }'
```
Meilisearch will return a summarized task object and place your request on the task queue:
```json theme={null}
{
"taskUid": 1,
"indexUid": "INDEX_NAME",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2024-03-04T15:05:43.383955Z"
}
```
Use the task object's `taskUid` to [monitor the task status](/docs/reference/api/tasks/get-task). The Hugging Face embedder will be ready to use once the task is completed.
## Conclusion
You have seen how to compile a Meilisearch binary that uses your Nvidia GPU to compute vector embeddings. Doing this should significantly speed up indexing when using Hugging Face.
# Self-hosting Meilisearch
Source: https://www.meilisearch.com/docs/resources/self_hosting/overview
Learn about self-hosting Meilisearch, from installation to production deployment, security, and backups.
Meilisearch is a single binary with no external dependencies. You can run it on any Linux, macOS, or Windows machine, on bare metal or in containers.
[Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=self-hosting-overview) is the recommended way to use Meilisearch. It handles provisioning, scaling, security, and updates for you. Self-hosting gives you full control over your infrastructure.
## When to self-host
Self-hosting Meilisearch makes sense when you need:
* **Full infrastructure control**: choose your own servers, network configuration, and storage
* **Data residency compliance**: keep data in specific geographic regions or on-premises
* **Custom deployment pipelines**: integrate Meilisearch into existing CI/CD and orchestration workflows
* **Air-gapped environments**: run Meilisearch in networks without internet access
## What you'll need
* A server running a [supported operating system](/docs/resources/self_hosting/getting_started/install_locally#supported-operating-systems)
* At least 256 MB of RAM (more for larger datasets, see [performance guide](/docs/resources/self_hosting/performance/ram_multithreading))
* The Meilisearch binary, available via [direct download, Docker, Homebrew, or APT](/docs/resources/self_hosting/getting_started/install_locally)
For production deployments, you will also need:
* A reverse proxy (Nginx or Caddy) for HTTPS termination
* A process manager (systemd) to keep Meilisearch running
* A [master key](/docs/resources/self_hosting/security/overview) for authentication
## Next steps
Install Meilisearch, add documents, and run your first search.
Deploy Meilisearch on AWS, GCP, or DigitalOcean.
Set up API keys, HTTPS, and access control.
Configure Meilisearch with CLI options, environment variables, or a config file.
# Impact of RAM and multi-threading on indexing performance
Source: https://www.meilisearch.com/docs/resources/self_hosting/performance/ram_multithreading
Adding new documents to a Meilisearch index is a multi-threaded and memory-intensive operation. Consult this article for more information on indexing performance.
Adding new documents to an index is a multi-threaded and memory-intensive operation. Meilisearch's indexes are at the core of what makes our search engine fast, relevant, and reliable. This article explains some of the details regarding RAM consumption and multi-threading.
## RAM
By default, our indexer uses the `sysinfo` Rust library to calculate a machine's total memory size. Meilisearch then adapts its behavior so indexing uses a maximum two thirds of available resources. Alternatively, you can use the [`--max-indexing-memory`](/docs/resources/self_hosting/configuration/reference#max-indexing-memory) instance option to manually control the maximum amount of RAM Meilisearch can consume.
It is important to prevent Meilisearch from using all available memory during indexing. If that happens, there are two negative consequences:
1. Meilisearch may be killed by the OS for over-consuming RAM
2. Search performance may decrease while the indexer is processing an update
Memory overconsumption can still happen in two cases:
1. When letting Meilisearch automatically set the maximum amount of memory used during indexing, `sysinfo` may not be able to calculate the amount of available RAM for certain OSes. Meilisearch still makes an educated estimate and adapts its behavior based on that, but crashes may still happen in this case. [Follow this link for an exhaustive list of OSes supported by `sysinfo`](https://docs.rs/sysinfo/0.20.0/sysinfo/#supported-oses)
2. Lower-end machines might struggle when processing huge datasets. Splitting your data payload into smaller batches can help in this case. [For more information, consult the section below](#memory-crashes)
## Multi-threading
In machines with multi-core processors, the indexer avoids using more than half of the available processing units. For example, if your machine has twelve cores, the indexer will try to use six of them at most. This ensures Meilisearch is always ready to perform searches, even while you are updating an index.
You can override Meilisearch's default threading limit by using the [`--max-indexing-threads`](/docs/resources/self_hosting/configuration/reference#max-indexing-threads) instance option. Allowing Meilisearch to use all processor cores for indexing might negatively impact your users' search experience.
Multi-threading is unfortunately not possible in machines with only one processor core.
## Memory crashes
In some cases, the OS will interrupt Meilisearch and stop all its processes. Most of these crashes happen during indexing and are a result of a machine running out of RAM. This means your computer does not have enough memory to process your dataset.
### Diagnosing memory issues
Before making changes, identify the root cause:
* **Check your `--max-indexing-memory` setting**: If you have manually configured [`--max-indexing-memory`](/docs/resources/self_hosting/configuration/reference#max-indexing-memory) to a value close to or exceeding your machine's total available RAM, Meilisearch may consume too much memory during indexing. Try lowering this value to leave room for the OS and other processes.
* **Monitor RSS usage**: Use tools such as `top`, `htop`, or `ps` to monitor the Resident Set Size (RSS) of the Meilisearch process during indexing. If RSS approaches the machine's total available memory, the OS may kill the process via the OOM (Out Of Memory) killer.
* **Evaluate dataset size relative to available RAM**: As a general guideline, your machine should have enough RAM to hold the full dataset in memory during indexing. If your dataset is significantly larger than available RAM, memory crashes become more likely.
* **Check system logs**: On Linux, inspect `dmesg` or `/var/log/syslog` for OOM killer messages. These logs confirm whether the OS terminated Meilisearch due to memory pressure.
### Mitigating memory crashes
If you are struggling with memory-related crashes, consider:
* Adding new documents in smaller batches to reduce peak memory consumption during indexing
* Lowering the [`--max-indexing-memory`](/docs/resources/self_hosting/configuration/reference#max-indexing-memory) value so Meilisearch reserves less memory for indexing
* Increasing your machine's RAM
* Reducing the number of searchable, filterable, and sortable attributes in your index settings, as each adds to indexing memory requirements
* [Following indexing best practices](/docs/capabilities/indexing/advanced/indexing_best_practices)
# Securing your project
Source: https://www.meilisearch.com/docs/resources/self_hosting/security/basic_security
This tutorial will show you how to secure your Meilisearch project.
This tutorial will show you how to secure your Meilisearch project. You will see how to manage your master key and how to safely send requests to the Meilisearch API using an API key.
## Creating the master key
The master key is the first and most important step to secure your Meilisearch project.
### Creating the master key in Meilisearch Cloud
Meilisearch Cloud automatically generates a master key for each project. This means Meilisearch Cloud projects are secure by default.
You can view your master key by visiting your project settings, then clicking "API Keys" on the sidebar:
### Creating the master key in a self-hosted instance
To protect your self-hosted instance, relaunch it using the `--master-key` command-line option or the `MEILI_MASTER_KEY` environment variable:
```sh theme={null}
./meilisearch --master-key="MASTER_KEY"
```
UNIX:
```sh theme={null}
export MEILI_MASTER_KEY="MASTER_KEY"
./meilisearch
```
Windows:
```sh theme={null}
set MEILI_MASTER_KEY="MASTER_KEY"
./meilisearch
```
The master key must be at least 16-bytes-long and composed of valid UTF-8 characters. Use one of the following tools to generate a secure master key:
* [`uuidgen`](https://www.digitalocean.com/community/tutorials/workflow-command-line-basics-generating-uuids)
* [`openssl rand`](https://www.openssl.org/docs/man1.0.2/man1/rand.html)
* [`shasum`](https://www.commandlinux.com/man-page/man1/shasum.1.html)
* [randomkeygen.com](https://randomkeygen.com/)
Meilisearch will launch as usual. The start up log should include a message informing you the instance is protected:
```
A master key has been set. Requests to Meilisearch won't be authorized unless you provide an authentication key.
```
If you supplied an insecure key, Meilisearch will display a warning and suggest you relaunch your instance with an autogenerated alternative:
```
We generated a new secure master key for you (you can safely use this token):
>> --master-key E8H-DDQUGhZhFWhTq263Ohd80UErhFmLIFnlQK81oeQ <<
Restart Meilisearch with the argument above to use this new and secure master key.
```
## Obtaining API keys
When your project is protected, Meilisearch automatically generates four API keys: `Default Search API Key`, `Default Admin API Key`, `Default Read-Only Admin API Key`, and `Default Chat API Key`. API keys are authorization tokens designed to safely communicate with the Meilisearch API.
### Obtaining API keys in Meilisearch Cloud
Find your API keys by visiting your project settings, then clicking "API Keys" on the sidebar:
### Obtaining API keys in a self-hosted instance
Use your master key to query the `/keys` endpoint to view all API keys in your instance:
```bash cURL theme={null}
curl \
-X GET 'MEILISEARCH_URL/keys' \
-H 'Authorization: Bearer MASTER_KEY'
```
```javascript JS theme={null}
const client = new MeiliSearch({ host: 'MEILISEARCH_URL', apiKey: 'masterKey' })
client.getKeys()
```
```python Python theme={null}
client = Client('MEILISEARCH_URL', 'masterKey')
client.get_keys()
```
```php PHP theme={null}
$client = new Client('MEILISEARCH_URL', 'masterKey');
$client->getKeys();
```
```java Java theme={null}
Client client = new Client(new Config("MEILISEARCH_URL", "masterKey"));
client.getKeys();
```
```ruby Ruby theme={null}
client = MeiliSearch::Client.new('MEILISEARCH_URL', 'masterKey')
client.keys
```
```go Go theme={null}
client := meilisearch.New("MEILISEARCH_URL", meilisearch.WithAPIKey("masterKey"))
client.GetKeys(nil);
```
```csharp C# theme={null}
MeilisearchClient client = new MeilisearchClient("MEILISEARCH_URL", "masterKey");
var keys = await client.GetKeysAsync();
```
```rust Rust theme={null}
let client = Client::new("MEILISEARCH_URL", Some("MASTER_KEY")); let keys = client .get_keys() .await .unwrap();
```
```swift Swift theme={null}
client = try MeiliSearch(host: "MEILISEARCH_URL", apiKey: "masterKey")
client.getKeys { result in
switch result {
case .success(let keys):
print(keys)
case .failure(let error):
print(error)
}
}
```
```dart Dart theme={null}
var client = MeiliSearchClient('MEILISEARCH_URL', 'masterKey');
await client.getKeys();
```
Only use the master key to manage API keys. Never use the master key to perform searches or other common operations.
Meilisearch's response will include at least the default API keys:
```json theme={null}
{
"results": [
{
"name": "Default Search API Key",
"description": "Use it to search from the frontend",
"key": "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33",
"uid": "74c9c733-3368-4738-bbe5-1d18a5fecb37",
"actions": [
"search"
],
"indexes": [
"*"
],
"expiresAt": null,
"createdAt": "2024-01-25T16:19:53.949636Z",
"updatedAt": "2024-01-25T16:19:53.949636Z"
},
{
"name": "Default Admin API Key",
"description": "Use it for anything that is not a search operation. Caution! Do not expose it on a public frontend",
"key": "62cdb7020ff920e5aa642c3d4066950dd1f01f4d",
"uid": "20f7e4c4-612c-4dd1-b783-7934cc038213",
"actions": [
"*"
],
"indexes": [
"*"
],
"expiresAt": null,
"createdAt": "2024-01-25T16:19:53.94816Z",
"updatedAt": "2024-01-25T16:19:53.94816Z"
},
{
"name": "Default Read-Only Admin API Key",
"description": "Use it to read information across the whole database. Caution! Do not expose this key on a public frontend",
"key": "9e32fb64e3569a749b0b87900d1026074e798743",
"uid": "7dc1ec09-94fb-49b5-b77b-03ce75af89a0",
"actions": [
"*.get",
"keys.get"
],
"indexes": [
"*"
],
"expiresAt": null,
"createdAt": "2024-01-25T16:19:53.94716Z",
"updatedAt": "2024-01-25T16:19:53.94716Z"
},
{
"name": "Default Chat API Key",
"description": "Use it to chat and search from the frontend",
"key": "0acaa4f3d57517e4b4d7c0052b02772620bd375a",
"uid": "d4e13ace-2a00-428c-90d1-b1c99eec98bd",
"actions": [
"chatCompletions",
"search"
],
"indexes": [
"*"
],
"expiresAt": null,
"createdAt": "2024-01-25T16:19:53.94606Z",
"updatedAt": "2024-01-25T16:19:53.94606Z"
}
],
…
}
```
## Sending secure API requests to Meilisearch
Now you have your API keys, you can safely query the Meilisearch API. Add API keys to requests using an `Authorization` bearer token header.
Use the `Default Admin API Key` to perform sensitive operations, such as creating a new index:
```bash cURL theme={null}
curl \
-X POST 'MEILISEARCH_URL/indexes' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer DEFAULT_ADMIN_API_KEY' \
--data-binary '{
"uid": "medical_records",
"primaryKey": "id"
}'
```
```rust Rust theme={null}
let client = Client::new("MEILISEARCH_URL", Some("DEFAULT_ADMIN_API_KEY"));
let task = client
.create_index("medical_records", Some("id"))
.await
.unwrap();
```
Then use the `Default Search API Key` to perform search operations in the index you just created:
```bash cURL theme={null}
curl \
-X POST 'MEILISEARCH_URL/indexes/medical_records/search' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer DEFAULT_SEARCH_API_KEY' \
--data-binary '{ "q": "appointments" }'
```
```rust Rust theme={null}
let client = Client::new("MEILISEARCH_URL", Some("DEFAULT_SEARCH_API_KEY"));
let index = client.index("medical_records");
index
.search()
.with_query("appointments")
.execute::()
.await
.unwrap();
```
### Admin API keys
Meilisearch provides two admin API keys for managing your instance:
* The `Default Admin API Key` grants full access to all Meilisearch operations except API key management. Use it to configure index settings, add documents, and perform other administrative tasks.
* The `Default Read-Only Admin API Key` allows read-only access to the whole database. Use it when you need to retrieve information from your Meilisearch instance without being able to modify it.
Do not expose admin API keys on a public frontend.
### Chat API key
The `Default Chat API Key` is designed for frontend usage with [conversational search](/docs/capabilities/conversational_search/getting_started/setup). It has access to both `search` and `chatCompletions` actions, allowing users to both perform searches and interact with the chat completions feature.
## Conclusion
You have successfully secured Meilisearch by configuring a master key. You then saw how to access the Meilisearch API by adding an API key to your request's authorization header.
# Using HTTP/2 and SSL with Meilisearch
Source: https://www.meilisearch.com/docs/resources/self_hosting/security/http2_ssl
Learn how to configure a server to use Meilisearch with HTTP/2.
For those willing to use HTTP/2, please be aware that it is **only possible if your server is configured with SSL certificate**.
Therefore, you will see how to launch a Meilisearch server with SSL. This tutorial gives a short introduction to do it locally, but you can as well do the same thing on a remote server.
First of all, you need the binary of Meilisearch, or you can also use docker. In the latter case, it is necessary to pass the parameters using environment variables and the SSL certificates via a volume.
A tool to generate SSL certificates is also required. In this How To, you will use [mkcert](https://github.com/FiloSottile/mkcert). However, if on a remote server, you can also use certbot or certificates signed by a Certificate Authority.
Then, use `curl` to do requests. It is a simple way to specify that you want to send HTTP/2 requests by using the `--http2` option.
## Try to use HTTP/2 without SSL
Start by running the binary.
```bash theme={null}
./meilisearch
```
And then, send a request.
```bash theme={null}
curl -kvs --http2 --request GET 'http://localhost:7700/indexes'
```
You will get the following answer from the server:
```bash theme={null}
* Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 7700 failed: Connection refused
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 7700 (#0)
> GET /indexes HTTP/1.1
> Host: localhost:7700
> User-Agent: curl/7.64.1
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA
>
< HTTP/1.1 200 OK
< content-length: 2
< content-type: application/json
< date: Fri, 17 Jul 2020 11:01:02 GMT
<
* Connection #0 to host localhost left intact
[]* Closing connection 0
```
You can see on line `> Connection: Upgrade, HTTP2-Settings` that the server tries to upgrade to HTTP/2, but is unsuccessful.
The answer `< HTTP/1.1 200 OK` indicates that the server still uses HTTP/1.
## Try to use HTTP/2 with SSL
This time, start by generating the SSL certificates. mkcert creates two files: `127.0.0.1.pem` and `127.0.0.1-key.pem`.
```bash theme={null}
mkcert '127.0.0.1'
```
Then, use the certificate and the key to configure Meilisearch with SSL.
```bash theme={null}
./meilisearch --ssl-cert-path ./127.0.0.1.pem --ssl-key-path ./127.0.0.1-key.pem
```
Next, make the same request as above but change `http://` to `https://`.
```bash theme={null}
curl -kvs --http2 --request GET 'https://localhost:7700/indexes'
```
You will get the following answer from the server:
```bash theme={null}
* Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 7700 failed: Connection refused
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 7700 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use h2
* Server certificate:
* subject: O=mkcert development certificate; OU=quentindequelen@s-iMac (Quentin de Quelen)
* start date: Jun 1 00:00:00 2019 GMT
* expire date: Jul 17 10:38:53 2030 GMT
* issuer: O=mkcert development CA; OU=quentindequelen@s-iMac (Quentin de Quelen); CN=mkcert quentindequelen@s-iMac (Quentin de Quelen)
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7ff601009200)
> GET /indexes HTTP/2
> Host: localhost:7700
> User-Agent: curl/7.64.1
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 4294967295)!
< HTTP/2 200
< content-length: 2
< content-type: application/json
< date: Fri, 17 Jul 2020 11:06:27 GMT
<
* Connection #0 to host localhost left intact
[]* Closing connection 0
```
You can see that the server now supports HTTP/2.
```bash theme={null}
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
```
The server successfully receives HTTP/2 requests.
```bash theme={null}
< HTTP/2 200
```
# Master key and API keys
Source: https://www.meilisearch.com/docs/resources/self_hosting/security/master_api_keys
Understand the differences between master key and API keys, and how to manage them in self-hosted Meilisearch.
This guide explains the differences between the master key and API keys, and how to manage them in self-hosted Meilisearch instances.
## Master key
The master key grants full control over your Meilisearch instance. It is the only key with access to endpoints for creating and deleting API keys by default. Since the master key is not an API key, it cannot be listed or configured through the `/keys` endpoints.
Exposing the master key can give malicious users complete control over your Meilisearch instance. **Only use the master key when managing API keys**, never for regular operations.
### Setting the master key
Launch Meilisearch with a master key to protect your instance:
```bash theme={null}
meilisearch --master-key="your-master-key-here"
```
```bash theme={null}
export MEILI_MASTER_KEY="your-master-key-here"
meilisearch
```
Your master key must be at least 16 bytes. Use a secure, randomly generated string.
### Resetting the master key
If your master key is compromised, reset it by relaunching your instance with a new value:
```bash theme={null}
meilisearch --master-key="new-master-key-here"
```
Resetting the master key automatically invalidates all existing API keys. You will need to create new API keys after resetting.
## API keys
API keys grant access to a specific set of indexes, routes, and endpoints. You can configure them to expire after a certain date. Use the [`/keys` route](/docs/reference/api/keys/list-api-keys) to create, configure, and delete API keys.
**Use API keys for all API operations except API key management.** This includes:
* Searching documents
* Adding and updating documents
* Configuring index settings
* Managing indexes
### Default API keys
When you launch Meilisearch with a master key, four default API keys are automatically created:
| Key | Purpose | Permissions |
| ------------------------------- | ------------------------------ | -------------------------------------------------------- |
| Default Search API Key | Frontend search queries | Search only, all indexes |
| Default Admin API Key | Backend operations | Full access except key management |
| Default Read-Only Admin API Key | Read-only access | Read-only access to all indexes, documents, and settings |
| Default Chat API Key | Frontend conversational search | Search and chat completions, all indexes |
In most cases, these default keys are sufficient:
* Use the **Default Search API Key** for client-side search
* Use the **Default Admin API Key** for server-side operations (do not expose on a public frontend)
* Use the **Default Read-Only Admin API Key** for read-only access to all indexes, documents, and settings (do not expose on a public frontend)
* Use the **Default Chat API Key** for [conversational search](/docs/capabilities/conversational_search/getting_started/setup) (can be safely used from the frontend)
### Creating custom API keys
Create custom API keys for more granular control:
```bash theme={null}
curl -X POST "${MEILISEARCH_URL}/keys" \
-H "Authorization: Bearer ${MASTER_KEY}" \
-H "Content-Type: application/json" \
-d '{
"description": "Search key for products index",
"actions": ["search"],
"indexes": ["products"],
"expiresAt": "2025-12-31T23:59:59Z"
}'
```
## Best practices
1. **Never expose the master key** in client-side code or public repositories
2. **Use API keys** for all regular operations
3. **Limit API key permissions** to only what's needed
4. **Set expiration dates** on API keys when appropriate
5. **Rotate keys regularly** in production environments
## Related resources
Full API documentation for key management
Learn about Meilisearch security model
# Securing self-hosted Meilisearch
Source: https://www.meilisearch.com/docs/resources/self_hosting/security/overview
Understand the Meilisearch security model, from master keys to API keys, and learn how to protect your instance.
Meilisearch uses a key-based authentication system to protect your data. Understanding how keys work is the first step to securing your instance.
## How authentication works
Meilisearch's security model has three layers:
1. **Master key**: a secret you set at launch. It is never used directly in API requests, but generates the default API keys
2. **API keys**: credentials used to authenticate API requests. Meilisearch creates two default keys (admin and search) when you set a master key
3. **Tenant tokens**: short-lived, client-side tokens derived from API keys. They enforce per-user search rules without exposing your API keys
```mermaid theme={null}
flowchart LR
MK[Master key] --> AK[API keys]
AK --> TT[Tenant tokens]
MK -.->|set at launch| MS[Meilisearch instance]
AK -->|authenticate requests| MS
TT -->|scoped search| MS
```
## Security checklist
For production self-hosted instances:
* [ ] Set a [master key](/docs/resources/self_hosting/security/master_api_keys) of at least 16 bytes
* [ ] Set the [environment to `production`](/docs/resources/self_hosting/configuration/reference#environment)
* [ ] Use HTTPS via a [reverse proxy](/docs/resources/self_hosting/deployment/running_production) or [direct SSL](/docs/resources/self_hosting/security/http2_ssl)
* [ ] Use the **search API key** (not the admin key) in front-end applications
* [ ] Consider [tenant tokens](/docs/capabilities/security/overview) for multi-tenant search
* [ ] Restrict network access with firewall rules
## Next steps
Understand the difference between master key and API keys, and how to manage them.
Step-by-step tutorial for setting up authentication on your instance.
Learn what happens when your instance has no master key.
Configure HTTPS directly on Meilisearch without a reverse proxy.
# Protected and unprotected Meilisearch projects
Source: https://www.meilisearch.com/docs/resources/self_hosting/security/protected_unprotected
This article explains the differences between protected and unprotected Meilisearch projects and instances.
This article explains the differences between protected and unprotected Meilisearch projects and instances.
## Protected projects
In protected projects, all Meilisearch API routes and endpoints can only be accessed by requests bearing an API key. The only exception to this rule is the `/health` endpoint, which may still be queried with unauthorized requests.
**Meilisearch Cloud projects are protected by default**. Self-hosted instances are only protected if you launch them with a master key.
Consult the [basic security tutorial](/docs/resources/self_hosting/security/basic_security) for instructions on how to communicate with protected projects.
## Unprotected projects
In unprotected projects and self-hosted instances, any user may access any API endpoint. Never leave a publicly accessible instance unprotected. Only use unprotected instances in safe development environments.
Meilisearch Cloud projects are always protected. Meilisearch self-hosted instances are unprotected by default.
# Resetting the master key
Source: https://www.meilisearch.com/docs/resources/self_hosting/security/resetting_master_key
This guide shows you how to reset the master key in Meilisearch Cloud and self-hosted instances.
This guide shows you how to manage the master key in Meilisearch Cloud and self-hosted instances. Resetting the master key may be necessary if an unauthorized party obtains access to your master key.
## Resetting the master key in Meilisearch Cloud
Meilisearch Cloud does not give users control over the master key. If you need to change your master key, contact support through the Cloud interface or on the official [Meilisearch Discord server](https://discord.meilisearch.com).
Resetting the master key automatically invalidates all API keys. Meilisearch Cloud will generate new default API keys automatically.
## Resetting the master key in self-hosted instances
To reset your master key in a self-hosted instance, relaunch your instance and pass a new value to `--master-key` or `MEILI_MASTER_KEY`.
Resetting the master key automatically invalidates all API keys. Meilisearch Cloud will generate new default API keys automatically.
# Configure replication for high availability
Source: https://www.meilisearch.com/docs/resources/self_hosting/sharding/configure_replication
Set up replicated shards across multiple Meilisearch instances to ensure high availability and distribute search load.
Replication assigns the same shard to multiple remotes in your Meilisearch network. This guide covers how to configure replication, common patterns, and scaling read throughput.
Replication requires the Meilisearch Enterprise Edition v1.37 or later and a [configured network](/docs/resources/self_hosting/sharding/setup_sharded_cluster).
## How replication works
When you configure shards, each shard can be assigned to one or more remotes. If a shard is assigned to multiple remotes, Meilisearch replicates the data to each of them. During a search, Meilisearch queries each shard exactly once, picking one of the available remotes for each shard (prioritizing the `self`/local remote). This avoids duplicate results.
## Assign shards to multiple remotes
To replicate a shard, list multiple remotes in its configuration:
```bash theme={null}
curl \
-X PATCH 'MEILISEARCH_URL/network' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
--data-binary '{
"shards": {
"shard-a": { "remotes": ["ms-00", "ms-01"] },
"shard-b": { "remotes": ["ms-01", "ms-02"] },
"shard-c": { "remotes": ["ms-02", "ms-00"] }
}
}'
```
In this configuration, every shard exists on two remotes. If any single instance goes down, all shard data still exists on another instance.
## Common replication patterns
### Full replication (every shard on every remote)
Best for small datasets where you want maximum availability and read throughput:
```json theme={null}
{
"shards": {
"shard-a": { "remotes": ["ms-00", "ms-01", "ms-02"] }
}
}
```
All three remotes hold the same data. This is effectively a read-replica setup: you get 3x the search capacity, and any two instances can go down without affecting availability.
### N+1 replication
Each shard on two remotes, spread across the cluster:
```json theme={null}
{
"shards": {
"shard-a": { "remotes": ["ms-00", "ms-01"] },
"shard-b": { "remotes": ["ms-01", "ms-02"] },
"shard-c": { "remotes": ["ms-02", "ms-00"] }
}
}
```
This is the recommended pattern for most use cases. It balances data redundancy, search throughput, and storage efficiency. Each instance holds 2 shards, and losing any single instance still leaves all shards available.
### Geographic replication
Place replicas in different regions to reduce latency for geographically distributed users:
```json theme={null}
{
"shards": {
"shard-a": { "remotes": ["us-east-01", "eu-west-01"] },
"shard-b": { "remotes": ["us-east-02", "eu-west-02"] }
}
}
```
Route search requests to the closest cluster. Both regions hold all data, so either can serve a full result set. By default, Meilisearch prioritizes local search requests and will not transfer the request to a remote server. Make sure your search requests are made on the closest remote instance to ensure this setup is efficient.
## Remote availability
When a network search runs, Meilisearch builds an internal set of remotes to query: it assigns each shard to a remote, then sends one query per remote with a shard filter. This guarantees that no shard is queried twice and that results are never duplicated.
Meilisearch supports automatic remote fallback. If the remote assigned to a shard is unreachable, the shard won't be queried, and another remote will be used to retrieve its content. However, if no remote is available for a given shard, that shard's results will be missing from the response. It's a best-effort approach.
## Scaling read throughput
Replication is the primary way to scale search throughput in Meilisearch. Each replica can independently handle search requests, so adding more replicas increases the total number of concurrent searches your cluster can handle.
To add a new replica for an existing shard, add the new remote and use `addRemotes` to append it to the shard without rewriting the full assignment:
```bash theme={null}
curl \
-X PATCH 'MEILISEARCH_URL/network' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
--data-binary '{
"remotes": {
"ms-03": {
"url": "http://ms-03.example.com:7703",
"searchApiKey": "SEARCH_KEY_03",
"writeApiKey": "WRITE_KEY_03"
}
},
"shards": {
"shard-a": { "addRemotes": ["ms-03"] }
}
}'
```
This triggers a `NetworkTopologyChange` task that replicates the shard's documents to `ms-03`.
## The leader instance
The leader is responsible for all write operations (document additions, settings changes, index management). Non-leader instances reject writes with a `not_leader` error.
If the leader goes down:
* **Search may be affected**: if search requests are routed to the downed leader, they will fail
* **Writes are blocked**: no documents can be added or updated until a leader is available. Note that alive remote instances continue to process tasks
* **Manual promotion**: you must designate a new leader by updating the network topology with `PATCH /network` and setting `"leader"` to another instance
There is no automatic leader election. If your leader goes down, you must manually promote a new one. Plan for this in your deployment strategy.
## Monitoring replica health
Check the current network topology to see which remotes are configured:
```bash theme={null}
curl \
-X GET 'MEILISEARCH_URL/network' \
-H 'Authorization: Bearer MEILISEARCH_KEY'
```
To verify a specific remote is responding, query it directly or use the health endpoint:
```bash theme={null}
curl 'http://ms-01.example.com:7701/health'
```
## Next steps
Start from scratch with a full cluster setup guide.
Add and remove remotes, update shard assignments.
Understand the concepts and feature compatibility.
Configure snapshots and dumps for your cluster.
# Manage the network
Source: https://www.meilisearch.com/docs/resources/self_hosting/sharding/manage_network
Add remotes, update shard assignments, and manage your Meilisearch network topology dynamically. Includes rebalancing behavior and validation rules.
Once your [sharded cluster is set up](/docs/resources/self_hosting/sharding/setup_sharded_cluster), you can modify the topology without restarting instances. All topology changes go through `PATCH /network` on the leader instance.
## Add a remote
Include the new remote in the `remotes` object. To assign it to an existing shard, either send the full `remotes` list for that shard, or use `addRemotes` as a convenience to append without rewriting the full list:
```bash theme={null}
curl \
-X PATCH 'MEILISEARCH_URL/network' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
--data-binary '{
"remotes": {
"ms-03": {
"url": "http://ms-03.example.com:7703",
"searchApiKey": "SEARCH_KEY_03",
"writeApiKey": "WRITE_KEY_03"
}
},
"shards": {
"shard-a": { "addRemotes": ["ms-03"] }
}
}'
```
`addRemotes` and `removeRemotes` are write-only convenience fields. They are applied on top of the existing shard configuration and are never returned by `GET /network`, which always returns the full `remotes` list for each shard.
## Update shard assignments
Each shard object in a `PATCH /network` request accepts three fields:
| Field | Type | Behavior |
| --------------- | ----- | ------------------------------------------------------------------ |
| `remotes` | array | Full replacement of the shard's remote list |
| `addRemotes` | array | Adds remotes to the existing list |
| `removeRemotes` | array | Removes remotes from the existing list, applied after `addRemotes` |
Shards not included in the request are left unchanged. To remove a remote from a specific shard:
```bash theme={null}
curl \
-X PATCH 'MEILISEARCH_URL/network' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
--data-binary '{
"shards": {
"shard-a": { "removeRemotes": ["ms-03"] }
}
}'
```
To fully replace a shard's assignment, use `remotes`:
```bash theme={null}
curl \
-X PATCH 'MEILISEARCH_URL/network' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
--data-binary '{
"shards": {
"shard-a": { "remotes": ["ms-00", "ms-01", "ms-03"] },
"shard-b": { "remotes": ["ms-01", "ms-02"] },
"shard-c": { "remotes": ["ms-02", "ms-03"] }
}
}'
```
## Topology changes and rebalancing
When you modify shard assignments, Meilisearch triggers a `NetworkTopologyChange` task on all remotes. This task runs in three steps:
1. **Compute new shards**: each instance uses rendezvous hashing on document IDs to determine which documents belong to which shard under the new topology.
2. **Export and import**: documents are sent to remotes that now own them.
3. **Delete stale data**: once all remotes confirm their imports are complete, each instance deletes the documents it no longer owns. Search switches to the new shard definitions at this point.
Cancelling a topology change at step 3 only results in stale documents being retained temporarily. It does not cause data loss.
Search requests may return incomplete results during a topology change. Wait for all `NetworkTopologyChange` tasks to complete before resuming normal search traffic.
Run the same Meilisearch version on all instances before rebalancing. Network rebalancing is not guaranteed to work across instances on different versions.
## Validation
`PATCH /network` rejects requests with a `400 invalid_network_shards` error in the following cases:
* The shard list would become empty after applying the patch
* A shard's `remotes` list would become empty after applying `removeRemotes`
* A shard references a remote that is not in the `remotes` object
* A remote is removed from `remotes` and this leaves a shard with no remotes
## Filter searches by shard
Target specific shards using the `_shard` filter in search requests:
```bash theme={null}
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
--data-binary '{
"q": "batman",
"filter": "_shard = \"shard-a\""
}'
```
Supported `_shard` filter operators:
| Syntax | Behavior |
| ---------------------------------- | ---------------------------------------------------- |
| `_shard = "shard-a"` | Documents associated to `shard-a` |
| `_shard != "shard-a"` | Documents associated to all shards except `shard-a` |
| `_shard IN ["shard-a", "shard-b"]` | Documents associated to both `shard-a` and `shard-b` |
## Attribute visibility via `/network`
If an attribute is not on the `displayedAttributes` list but is present on `sortableAttributes`, its value can become publicly accessible through the `/network` endpoint. Do not enable the `network` feature if you rely on the value of attributes not present in `displayedAttributes` to remain hidden at all times. Either add such attributes to `displayedAttributes` so their exposure is explicit, or remove them from `sortableAttributes` before opting into network search.
## Private network security
By default, Meilisearch blocks requests to non-global IP addresses. If your instances communicate over a private network, configure the `--experimental-allowed-ip-networks` flag on each instance:
```bash theme={null}
meilisearch --experimental-allowed-ip-networks 10.0.0.0/8,192.168.0.0/16
```
Only allow the CIDR ranges your instances actually use.
## Next steps
Understand the concepts behind sharding, replication, and network search.
Deploy Meilisearch to production on various cloud providers.
# Replication and sharding
Source: https://www.meilisearch.com/docs/resources/self_hosting/sharding/overview
Scale Meilisearch horizontally by distributing documents across multiple instances with sharding, and ensure high availability with replication.
Replication and sharding let you run Meilisearch across multiple instances as a coordinated network. Sharding splits your data across instances so each one handles a smaller portion. Replication duplicates shards across instances so your search stays available if one goes down.
Replication and sharding require the Meilisearch Enterprise Edition v1.37 or later. See [Enterprise and Community editions](/docs/resources/self_hosting/enterprise_edition) for details.
## What is sharding?
Sharding distributes documents from a single index across multiple Meilisearch instances, called "remotes." Each remote holds one or more named shards containing a subset of your documents.
When a user searches, Meilisearch queries the necessary remotes in the network, collects results from each shard, and merges them into a single ranked response, as if the data lived on a single machine.
## What is replication?
Replication assigns the same shard to more than one remote. This ensures your data is stored redundantly across instances. During a network search, Meilisearch ensures each shard is queried exactly once, either from a remote shard or from the local one (chosen randomly, favoring the local one). This guarantees each shard is queried exactly once, so results are never duplicated regardless of how many replicas exist.
## How it works
```mermaid theme={null}
graph TD
Client[Client application] -->|search with useNetwork: true| Any[Any instance]
Any -->|fan out| R1[Remote ms-00
shard-a, shard-c]
Any -->|fan out| R2[Remote ms-01
shard-a, shard-b]
Any -->|fan out| R3[Remote ms-02
shard-b, shard-c]
R1 -->|partial results| Any
R2 -->|partial results| Any
R3 -->|partial results| Any
Any -->|merged results| Client
```
1. **Network**: the user configures the topology via `/network` on the leader, and this instance propagates it to all remotes
2. **Shards**: Remotes distribute the subsets of documents across themselves based on shard assignments
3. **Search**: when `useNetwork: true` is set or not defined (defaults to `true`), the instance receiving the request fans out the search to all remotes, then merges and ranks the combined results
## When to use sharding and replication
| Scenario | Solution |
| --------------------------------------- | --------------------------------------------------------- |
| Dataset too large for a single instance | **Sharding**: split documents across multiple remotes |
| Need high availability | **Replication**: assign each shard to 2+ remotes |
| Geographic distribution | **Sharding + replication**: place remotes closer to users |
| Read throughput bottleneck | **Replication**: distribute search load across replicas |
## The network
All instances in a Meilisearch network share a topology configuration that defines:
* **`self`**: the identity of the current instance
* **`leader`**: the instance coordinating writes and topology changes
* **`remotes`**: all instances in the network with their URLs, search API keys, and write API keys
* **`shards`**: how document subsets are distributed across remotes
The leader instance is responsible for write operations and topology changes. Non-leader instances reject write requests (document additions, settings changes, index creation) with a `not_leader` error. Search requests can be sent to any instance in the network.
## Searching across the network
To search across all instances:
`useNetwork` defaults to `true` when a network topology is defined.
```bash theme={null}
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
--data-binary '{
"q": "batman"
}'
```
The response includes `_federation` metadata showing which remote each result came from. You can also use the `_shard` filter to target specific shards:
```json theme={null}
{
"q": "batman",
"filter": "_shard = \"shard-a\""
}
```
### Network search with multi-search
Network search works with [multi-search](/docs/capabilities/multi_search/getting_started/federated_search) and [federated search](/docs/capabilities/multi_search/getting_started/federated_search). Add `useNetwork: true` to individual queries within a multi-search request:
```bash theme={null}
curl \
-X POST 'MEILISEARCH_URL/multi-search' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
--data-binary '{
"queries": [
{ "indexUid": "movies", "q": "batman" },
{ "indexUid": "comics", "q": "batman" }
]
}'
```
## Feature compatibility
Most Meilisearch features work transparently across a sharded network. The following table highlights important considerations:
| Feature | Works with sharding? | Notes |
| ---------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Full-text search | Yes | Results merged and ranked across all remotes |
| Filtering and sorting | Yes | Filters applied on each remote before merging |
| Faceted search | Yes | Facet distribution in search results works across remotes, and the `/facet-search` endpoint supports `useNetwork` |
| Hybrid/semantic search | Yes | Each remote runs its own vector search, results merged |
| Geo search | Yes | Geographic filters and sorting work across remotes |
| Multi-search | Yes | Works per query; `useNetwork` defaults to `true` when a network is configured |
| Federated search | Yes | Federation merges results from both indexes and remotes |
| Analytics | Partial | Events are tracked on the instance that receives the search request |
| Tenant tokens | Yes | Token filters apply on each remote |
| Document operations | Leader only | Writes must go through the leader instance |
| Settings changes | Leader only | Settings updates must go through the leader |
| Conversational search | No | Chat completions do not support `useNetwork` |
Search requests may return errors during a network topology change if they reference shards that are being added or removed. Wait for all `NetworkTopologyChange` tasks to complete before searching.
## Prerequisites
Before setting up sharding and replication, you need:
* Meilisearch Enterprise Edition v1.37 or later on all instances
* A master key configured on each instance
* Network connectivity between all instances
* If using private networks (`10.x.x.x`, `192.168.x.x`), the `--experimental-allowed-ip-networks` flag must be set on each instance
Run the same Meilisearch version on all instances. Internal communication between instances has had no breaking changes so far, so instances on different versions can currently communicate, but cross-version compatibility is not guaranteed in future versions. Search and federated features remain compatible on a best-effort basis, while network rebalancing requires all instances on the same version (see [Manage the network](/docs/resources/self_hosting/sharding/manage_network)).
## Next steps
Step-by-step guide to configuring sharding and replication.
Set up replicated shards for high availability and read scaling.
Add and remove remotes, update shard assignments.
Learn about the differences between Community and Enterprise editions.
# Set up a sharded cluster
Source: https://www.meilisearch.com/docs/resources/self_hosting/sharding/setup_sharded_cluster
Configure Meilisearch instances into a sharded cluster with replication for horizontal scaling and high availability.
This guide walks you through setting up a Meilisearch cluster with three instances, three shards, and replication for redundancy.
Sharding requires the Meilisearch Enterprise Edition v1.37 or later.
## Step 1: Start your instances
Start three Meilisearch instances, each with a master key:
```bash theme={null}
# Instance ms-00
meilisearch --master-key MEILISEARCH_KEY_00 --http-addr 0.0.0.0:7700
# Instance ms-01
meilisearch --master-key MEILISEARCH_KEY_01 --http-addr 0.0.0.0:7701
# Instance ms-02
meilisearch --master-key MEILISEARCH_KEY_02 --http-addr 0.0.0.0:7702
```
If your instances communicate over a private network, add the `--experimental-allowed-ip-networks` flag:
```bash theme={null}
meilisearch --master-key MEILISEARCH_KEY --experimental-allowed-ip-networks 10.0.0.0/8,192.168.0.0/16
```
## Step 2: Enable the network feature
Enable the experimental network feature on each instance:
```bash theme={null}
curl \
-X PATCH 'http://ms-00.example.com:7700/experimental-features' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer MEILISEARCH_KEY_00' \
--data-binary '{ "network": true }'
```
Repeat for `ms-01` and `ms-02` with their respective URLs and master keys.
## Step 3: Configure the network topology
Send a single `PATCH /network` request to the leader instance (`ms-00`). The leader propagates the configuration to all other remotes automatically. For initial setup, define `self`, `leader`, `remotes`, and `shards` together:
```bash theme={null}
curl \
-X PATCH 'http://ms-00.example.com:7700/network' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer MEILISEARCH_KEY_00' \
--data-binary '{
"leader": "ms-00",
"self": "ms-00",
"remotes": {
"ms-00": {
"url": "http://ms-00.example.com:7700",
"searchApiKey": "SEARCH_KEY_00",
"writeApiKey": "WRITE_KEY_00"
},
"ms-01": {
"url": "http://ms-01.example.com:7701",
"searchApiKey": "SEARCH_KEY_01",
"writeApiKey": "WRITE_KEY_01"
},
"ms-02": {
"url": "http://ms-02.example.com:7702",
"searchApiKey": "SEARCH_KEY_02",
"writeApiKey": "WRITE_KEY_02"
}
},
"shards": {
"shard-a": { "remotes": ["ms-00"] },
"shard-b": { "remotes": ["ms-01"] },
"shard-c": { "remotes": ["ms-02"] }
}
}'
```
In this configuration, each shard lives on exactly one remote. Documents are distributed across all three instances, and each instance handles searches for its own shards.
This setup has no replication. If a remote becomes unavailable, its shards are missing from search results. Meilisearch does not yet automatically fall back to another instance. See [Configure replication](/docs/resources/self_hosting/sharding/configure_replication) for a high-availability setup.
## Step 4: Index documents
Send documents to the leader instance (`ms-00`). The leader distributes them across shards automatically:
```bash theme={null}
curl \
-X POST 'http://ms-00.example.com:7700/indexes/movies/documents' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer MEILISEARCH_KEY_00' \
--data-binary '[
{ "id": 1, "title": "Batman Begins" },
{ "id": 2, "title": "The Dark Knight" },
{ "id": 3, "title": "Spider-Man" }
]'
```
All write operations (document additions, updates, deletions, settings changes) must go through the leader instance. Non-leader instances reject writes with a `not_leader` error.
## Step 5: Search across the cluster
Search requests can be sent to any instance in the network, not just the leader. `useNetwork` defaults to `true` when a network topology is defined:
```bash theme={null}
curl \
-X POST 'http://ms-00.example.com:7700/indexes/movies/search' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer SEARCH_KEY_00' \
--data-binary '{
"q": "batman"
}'
```
Meilisearch fans out the search to all shards, collects results from each shard, and returns a single merged response.
## Verify the topology
Check the current network configuration at any time:
```bash theme={null}
curl \
-X GET 'http://ms-00.example.com:7700/network' \
-H 'Authorization: Bearer MEILISEARCH_KEY_00'
```
## Next steps
Add and remove remotes dynamically without reconfiguring the entire topology.
Understand the concepts behind sharding, replication, and network search.
Configure snapshots and dumps for your cluster.
# Using task webhooks
Source: https://www.meilisearch.com/docs/resources/self_hosting/webhooks
Learn how to use webhooks to react to changes in your Meilisearch database.
This guide teaches you how to configure a single webhook via instance options to notify a URL when Meilisearch completes a [task](/docs/capabilities/indexing/tasks_and_batches/async_operations).
If you are using Meilisearch Cloud or need to configure multiple webhooks, use the [`/webhooks` API route](/docs/reference/api/webhooks) instead.
## Requirements
* a command-line console
* a self-hosted Meilisearch instance
* a server configured to receive `POST` requests with an ndjson payload
## Configure the webhook URL
🚩 To be able to configure a webhook to notify internal services (such as `localhost`), you will need to [allow requests on private networks](/docs/resources/self_hosting/configuration/overview#allow-requests-to-private-networks). 🚩
Restart your Meilisearch instance and provide the webhook URL to `--task-webhook-URL`:
```sh theme={null}
meilisearch --task-webhook-url http://localhost:8000
```
You may also define the webhook URL with environment variables or in the configuration file with `MEILI_TASK_WEBHOOK_URL`.
## Limits and constraints
You can create up to 20 webhooks per instance via the [`/webhooks` API route](/docs/reference/api/webhooks). Having many webhooks active at the same time may negatively impact performance, so only register the webhooks you actively need.
The value of `Authorization` headers is redacted in responses from `GET /webhooks` and `GET /webhooks/{uuid}`. Do not use the redacted header values returned by Meilisearch when updating a webhook, or the webhook will start sending invalid credentials to your endpoint. Store the original secret on your side and resend it explicitly whenever you patch the webhook.
Meilisearch Cloud may create internal webhooks to support features such as Analytics and monitoring. These Cloud-reserved webhooks are always returned with `isEditable: false` and cannot be updated or deleted through the API.
## Optional: configure an authorization header and allow requests on private networks
Depending on your setup, you may need to provide an authorization header and allow requests on private networks.
Provide these using `task-webhook-authorization-header` and `experimental-allowed-ip-networks`:
```sh theme={null}
meilisearch \
--task-webhook-url http://localhost:8000 \
--task-webhook-authorization-header Bearer aSampleMasterKey \
--experimental-allowed-ip-networks 127.0.0.0/8
```
## Test the webhook
A common asynchronous operation is adding or updating documents to an index. The following example adds a test document to our `movies` index:
```bash cURL theme={null}
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/documents' \
-H 'Content-Type: application/json' \
--data-binary '[
{
"id": 287947,
"title": "Shazam",
"poster": "https://image.tmdb.org/t/p/w1280/xnopI5Xtky18MPhK40cZAGAOVeV.jpg",
"overview": "A boy is given the ability to become an adult superhero in times of need with a single magic word.",
"release_date": "2019-03-23"
}
]'
```
```javascript JS theme={null}
client.index('movies').addDocuments([{
id: 287947,
title: 'Shazam',
poster: 'https://image.tmdb.org/t/p/w1280/xnopI5Xtky18MPhK40cZAGAOVeV.jpg',
overview: 'A boy is given the ability to become an adult superhero in times of need with a single magic word.',
release_date: '2019-03-23'
}], { skipCreation: true })
```
```python Python theme={null}
client.index('movies').add_documents([{
'id': 287947,
'title': 'Shazam',
'poster': 'https://image.tmdb.org/t/p/w1280/xnopI5Xtky18MPhK40cZAGAOVeV.jpg',
'overview': 'A boy is given the ability to become an adult superhero in times of need with a single magic word.',
'release_date': '2019-03-23'
}], skip_creation=True)
```
```php PHP theme={null}
$client->index('movies')->addDocuments([
[
'id' => 287947,
'title' => 'Shazam',
'poster' => 'https://image.tmdb.org/t/p/w1280/xnopI5Xtky18MPhK40cZAGAOVeV.jpg',
'overview' => 'A boy is given the ability to become an adult superhero in times of need with a single magic word.',
'release_date' => '2019-03-23'
]
]);
```
```java Java theme={null}
client.index("movies").addDocuments("[{"
+ "\"id\": 287947,"
+ "\"title\": \"Shazam\","
+ "\"poster\": \"https://image.tmdb.org/t/p/w1280/xnopI5Xtky18MPhK40cZAGAOVeV.jpg\","
+ "\"overview\": \"A boy is given the ability to become an adult superhero in times of need with a single magic word.\","
+ "\"release_date\": \"2019-03-23\""
+ "}]"
);
```
```ruby Ruby theme={null}
client.index('movies').add_documents([
{
id: 287947,
title: 'Shazam',
poster: 'https://image.tmdb.org/t/p/w1280/xnopI5Xtky18MPhK40cZAGAOVeV.jpg',
overview: 'A boy is given the ability to become an adult superhero in times of need with a single magic word.',
release_date: '2019-03-23'
}
])
```
```go Go theme={null}
documents := []map[string]interface{}{
{
"id": 287947,
"title": "Shazam",
"poster": "https://image.tmdb.org/t/p/w1280/xnopI5Xtky18MPhK40cZAGAOVeV.jpg",
"overview": "A boy is given the ability to become an adult superhero in times of need with a single magic word.",
"release_date": "2019-03-23",
},
}
options := &meilisearch.DocumentOptions{SkipCreation: false}
client.Index("movies").AddDocuments(documents, options)
```
```csharp C# theme={null}
var movie = new[]
{
new Movie
{
Id = "287947",
Title = "Shazam",
Poster = "https://image.tmdb.org/t/p/w1280/xnopI5Xtky18MPhK40cZAGAOVeV.jpg",
Overview = "A boy is given the ability to become an adult superhero in times of need with a single magic word.",
ReleaseDate = "2019-03-23"
}
};
await index.AddDocumentsAsync(movie);
```
```rust Rust theme={null}
let task: TaskInfo = client
.index("movies")
.add_or_replace(&[
Movie {
id: 287947,
title: "Shazam".to_string(),
poster: "https://image.tmdb.org/t/p/w1280/xnopI5Xtky18MPhK40cZAGAOVeV.jpg".to_string(),
overview: "A boy is given the ability to become an adult superhero in times of need with a single magic word.".to_string(),
release_date: "2019-03-23".to_string(),
}
], None)
.await
.unwrap();
```
```swift Swift theme={null}
let documentJsonString = """
[
{
"reference_number": 287947,
"title": "Shazam",
"poster": "https://image.tmdb.org/t/p/w1280/xnopI5Xtky18MPhK40cZAGAOVeV.jpg",
"overview": "A boy is given the ability to become an adult superhero in times of need with a single magic word.",
"release_date": "2019-03-23"
}
]
"""
let documents: Data = documentJsonString.data(using: .utf8)!
client.index("movies").addDocuments(documents: documents) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart theme={null}
await client.index('movies').addDocuments([
{
'id': 287947,
'title': 'Shazam',
'poster':
'https://image.tmdb.org/t/p/w1280/xnopI5Xtky18MPhK40cZAGAOVeV.jpg',
'overview':
'A boy is given the ability to become an adult superhero in times of need with a single magic word.',
'release_date': '2019-03-23'
}
]);
```
When Meilisearch finishes indexing this document, it will send a `POST` request the URL you configured with `--task-webhook-url`. The request body will be one or more task objects in [ndjson](https://github.com/ndjson/ndjson-spec) format:
```ndjson theme={null}
{"uid":4,"batchUid":4,"indexUid":"movies","status":"succeeded","type":"documentAdditionOrUpdate","canceledBy":null,"details":{"receivedDocuments":1,"indexedDocuments":1},"duration":"PT0.001192S","enqueuedAt":"2022-08-04T12:28:15.159167Z","startedAt":"2022-08-04T12:28:15.161996Z","finishedAt":"2022-08-04T12:28:15.163188Z"}
```
If Meilisearch has batched multiple tasks, it will only trigger the webhook once all tasks in a batch are finished. In this case, the response payload will include all tasks, each separated by a new line:
```ndjson theme={null}
{"uid":4,"batchUid":4,"indexUid":"movies","status":"succeeded","type":"documentAdditionOrUpdate","canceledBy":null,"details":{"receivedDocuments":1,"indexedDocuments":1},"duration":"PT0.001192S","enqueuedAt":"2022-08-04T12:28:15.159167Z","startedAt":"2022-08-04T12:28:15.161996Z","finishedAt":"2022-08-04T12:28:15.163188Z"}
{"uid":5,"batchUid":4,"indexUid":"movies","status":"succeeded","type":"documentAdditionOrUpdate","canceledBy":null,"details":{"receivedDocuments":1,"indexedDocuments":1},"duration":"PT0.001192S","enqueuedAt":"2022-08-04T12:28:15.159167Z","startedAt":"2022-08-04T12:28:15.161996Z","finishedAt":"2022-08-04T12:28:15.163188Z"}
{"uid":6,"batchUid":4,"indexUid":"movies","status":"succeeded","type":"documentAdditionOrUpdate","canceledBy":null,"details":{"receivedDocuments":1,"indexedDocuments":1},"duration":"PT0.001192S","enqueuedAt":"2022-08-04T12:28:15.159167Z","startedAt":"2022-08-04T12:28:15.161996Z","finishedAt":"2022-08-04T12:28:15.163188Z"}
```
# Changelog
Source: https://www.meilisearch.com/docs/changelog/changelog
New features and improvements in Meilisearch
## New Features
**SSE streaming routes for tasks and batches (experimental)**
Two new Server-Sent Events (SSE) routes allow you to subscribe to live updates instead of polling:
* `GET /tasks/stream`: streams task status updates in real time
* `GET /batches/stream`: streams batch status updates in real time
These routes are experimental. Enable them with the `tasksStreamingRoute` experimental feature flag before use.
## Improvements
**Faster document retrieval**
Document formatting has been optimized from O(n) to O(1) complexity, delivering significant speed improvements when retrieving large numbers of documents (more than 20 items).
## Bug Fixes
* Fixed duplicate pins appearing in federated search results
* Fixed unnecessary settings updates when configuration remains unchanged
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.52.0)
## New Features
**Filter conditions for Dynamic Search Rules**
Dynamic Search Rules now support a `filter` condition. A rule activates when the search filter matches at least one branch of the condition, giving you finer control over when rules apply.
**New `lastUpdatedAt` field for Dynamic Search Rules**
DSR listings now include a `lastUpdatedAt` field automatically populated with the timestamp of the last modification task. Listings are also sorted by `lastUpdatedAt` in descending order (most recently modified first).
## Improvements
**Faster search on large datasets**
Disk reads have been reduced to a single operation across the entire search pipeline, yielding up to 5.4x performance improvements on datasets with many distinct fields.
**Dumpless upgrade stabilized**
The `--experimental-dumpless-upgrade` flag is now stable and has been renamed `--upgrade-db`. Functionality is unchanged.
## Bug Fixes
* Restored legacy shorthand filterable attributes syntax support
## Breaking Changes
* Removed `--experimental-replication-parameters`, `--experimental-no-edition-2024-for-dumps`, and `--experimental-no-snapshot-compaction` flags
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.51.0)
## New Features
**Scalable Dynamic Search Rules**
The Dynamic Search Rules API has been revamped to scale to 75,000 rules without impacting search performance. Key changes include:
* New `DELETE /dynamic-search-rules` route to delete all rules at once
* `priority` field renamed to `precedence`
* `conditions` restructured with separate `query` and `time` condition objects
* A DSR Fuel system with configurable limits via environment variables
**Federated document fetching in sharded configurations**
Document retrieval routes now fetch from all shards by default in network configurations. Use the new `useNetwork: false` parameter to limit retrieval to the local instance.
**Facet wildcard support**
The `facets` search parameter now accepts wildcard patterns (for example, `dogs.*`) to match multiple facet fields at once.
## Breaking Changes
* `PATCH` and `DELETE /dynamic-search-rules/{uid}` now return tasks instead of immediate responses
* `DELETE /dynamic-search-rules/{uid}` no longer returns 404 for non-existent rules
* The `POST /dynamic-search-rules` filter parameter uses `query` instead of `attributePatterns`
## Bug Fixes
* Fixed migration failures when upgrading from v1.48 with empty synonyms
* Fixed quadratic memory consumption in filter processing
* Fixed escaped character handling in filter expressions
* Improved fault tolerance for AWS S3 multipart snapshot uploads
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.50.0)
## Improvements
**Faster synonym search**
Meilisearch now loads synonyms lazily, only when a query word actually matches a synonym. Previously, all synonyms were loaded for every search request regardless of relevance, which caused noticeable slowdowns for instances with large synonym lists. Depending on your synonym count, this can deliver performance improvements of up to 13x.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.49.0)
## New Features
**Render template route (experimental)**
A new `POST /render-template` route lets you test document templates and fragments before and after configuring an embedder. This is useful for validating template syntax and seeing how your documents will be rendered.
Before using this route, enable the `renderRoute` experimental feature:
```bash theme={null}
PATCH /experimental-features
```
```json theme={null}
{
"renderRoute": true
}
```
The route accepts a template and optional input, and returns both the unrendered template and the rendered result:
```http theme={null}
POST /render-template
```
```json theme={null}
{
"template": {
"kind": "documentTemplate",
"indexUid": "movies",
"embedder": "myMoviesEmbedder"
},
"input": {
"kind": "indexDocument",
"indexUid": "movies",
"id": "2"
}
}
```
Response:
```json theme={null}
{
"template": "A movie titled {{doc.title}} whose description starts with {{doc.overview|truncatewords:10}}",
"rendered": "A movie titled Ariel whose description starts with Taisto Kasurinen is a Finnish coal miner whose father has..."
}
```
You can render templates from embedders, chat settings, or inline templates. You can also provide input from index documents, inline documents, or search queries. If `input` is `null`, the route returns just the template without rendering it.
## Other
**Foreign filters restricted to retrieval routes**
Foreign filters are now only supported on retrieval routes (search, get document, etc.). They are no longer accepted on routes that write or modify documents. The following routes no longer support foreign filters:
* Edit documents by function: `POST /indexes/{index_uid}/documents/edit`
* Delete documents by filter: `POST /indexes/{index_uid}/documents/delete`
* Export to a remote Meilisearch: `POST /export`
**Documents fetch queue feature inverted**
The `queueDocumentsFetch` experimental feature has been replaced with `disableDocumentsFetchQueue`. This changes the behavior from opt-in to opt-out. The documents fetch queue is now enabled by default and you must explicitly disable it if needed.
**Bug fixes**
* Remote federated search no longer returns duplicate documents from different instances.
* S3 snapshot uploads no longer fail due to a race condition in internal buffer recycling.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.48.0)
## New Features
**Search personalization on federated search**
You can now use [search personalization](/docs/capabilities/personalization/overview) in federated search requests. As with `page`/`hitsPerPage` and `limit`/`offset`, the personalization option must be set inside the `federation` object. If you place it on an individual query instead, Meilisearch returns an error reminding you to move it into `federation`.
## Improvements
**The new settings indexer is now feature complete**
The new settings indexer now handles tokenizer-related settings, including locales, dictionary, synonyms, stop words, separator tokens, and non-separator tokens. With this addition, all settings tasks are handled by the new indexer unless you set `MEILI_EXPERIMENTAL_NO_EDITION_2024_FOR_SETTINGS=true`. This brings better scaling behavior, much faster task cancellation, and a more precise progress view when updating settings.
**More observability metrics**
Meilisearch now exposes additional Prometheus metrics, including document throughput, making it easier to monitor indexing performance and debug your instance.
## Other
**Search pipeline refactor**
The search pipeline has been refactored so that all search requests run through a unified federated search implementation under the hood. This introduces a small breaking change: some error messages have been updated and a few error codes have changed (for example, in certain cases `MultiSearchError` may now be returned where `SearchError` was previously returned, and vice versa). If your application inspects error codes, review your error handling when upgrading.
**Bug fixes**
* Placing `attributeRank` or `wordPosition` before `words` in `rankingRules` no longer removes hits from the response.
* `searchCutoffMs` is no longer ignored under certain conditions when embedding documents.
* Remote federated search and `useNetwork: true` requests no longer fail when a filter contains a single quote (`'`).
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.47.0)
## New Features
**Queue document fetch routes (experimental)**
A new experimental feature, `queueDocumentsFetch`, makes the `GET /indexes/:uid/documents` and `POST /indexes/:uid/documents/fetch` routes wait in the search queue when no thread is available to process them, improving stability under heavy load.
## Improvements
**Expanded new settings indexer support**
The new settings indexer now handles more parameters, so changing them no longer requires a full re-indexing:
* Exact words and disable-on-words
* Exact and disable settings on numbers
* Prefix search settings (prefix computation)
This makes the engine more efficient when updating these settings.
## Other
**Fixed deletion batching regression**
Fixed a regression introduced in v1.45.0 affecting the auto-batching of deletion by filter together with document additions and updates. This operation is now batched correctly.
**Fixed S3 multipart upload part size**
Meilisearch now respects the configured multipart part size when uploading snapshots to S3, never creating a part larger than the defined size (except for the last part).
**Fixed panic on incomplete filters**
An incomplete filter now returns an error instead of causing an internal panic.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.46.0)
## Improvements
**Faster settings indexing performance**
Meilisearch now handles more settings through the optimized settings indexer, significantly improving performance when changing settings. The following settings are now directly handled by the new indexer without requiring a full re-indexing:
* Displayed fields
* Synonyms
* Primary key
* Typo tolerance settings (authorize typos, min word length for one and two typos)
* Facet settings (max values per facet, sort facet values by)
* Pagination max total hits
* Search cutoff
* Chat settings
* Foreign keys
* Global facet search
If you encounter any issues with the new settings indexer, you can disable it by setting the environment variable `MEILI_EXPERIMENTAL_NO_EDITION_2024_FOR_SETTINGS=true` or using the equivalent CLI parameter.
**Improved document fetch performance**
Document fetching no longer blocks Actix worker threads, resulting in better overall system responsiveness when retrieving documents.
**Optimized deletion batching**
Meilisearch now more efficiently batches filter-based deletions when mixed with document additions, improving indexing performance for this operation pattern. However, deleting documents by ID remains the recommended approach for optimal performance.
## Other
**Fixed binary quantization configuration corruption**
Resolved an issue where changing the binary quantization setting in embedder configurations would corrupt the database, preventing future changes to the quantization. If your database was affected by this issue, you may need to recreate the binary-quantized embedder from scratch.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.45.0)
## New Features
### Remote federated facet search
Facet search now supports searching across all shards in a network. When using the `network` experimental feature with a `leader` defined, facet search calls to `POST /indexes/{indexUid}/facet-search` now default to remote federated search, fetching and merging results from all shards.
You can control this behavior explicitly using the new `useNetwork` parameter in the facet search request body.
### Human-formatted database sizes and detailed internal database breakdown
The stats endpoints now support two new query parameters to give you better visibility into your index storage:
* `showInternalDatabaseSizes`: When set to `true`, index stats include an `internalDatabaseSizes` object showing the size of each internal database component
* `sizeFormat`: Set to `human` to get human-readable sizes (MiB, GiB, etc.) instead of bytes
Example with both parameters:
```bash theme={null}
curl -X GET "http://localhost:7700/indexes/movies/stats?showInternalDatabaseSizes=true&sizeFormat=human"
```
```json theme={null}
{
"numberOfDocuments": 31944,
"rawDocumentDbSize": "19.64 MiB",
"avgDocumentSize": "636 B",
"isIndexing": false,
"internalDatabaseSizes": {
"wordPairProximityDocids": "96.16 MiB",
"documents": "19.64 MiB",
"wordPositionDocids": "17.83 MiB",
"wordFidDocids": "10.22 MiB",
"wordPrefixPositionDocids": "9.78 MiB",
"wordDocids": "9.02 MiB",
"wordPrefixFidDocids": "4.39 MiB",
"wordPrefixDocids": "3.27 MiB",
"main": "1.36 MiB",
"externalDocumentsIds": "976 KiB",
"fieldIdWordCountDocids": "240 KiB",
"exactWordPrefixDocids": "16 KiB",
"celluliteMetadata": "16 KiB"
},
"fieldDistribution": {
"genres": 31944,
"id": 31944,
"overview": 31944,
"poster": 31944,
"release_date": 31944,
"title": 31944
}
}
```
The same parameters work with `GET /stats` for global statistics.
## Improvements
### Reduced memory usage during indexing
Indexing memory consumption has been reduced through optimizations in prefix computation and by avoiding unnecessary deserialization. If you still experience high memory usage during post-processing, enable the `--experimental-reduce-indexing-memory-usage` option.
### Improved GeoJSON indexing performance
GeoJSON indexing is now faster and more efficient. The optimization avoids reprocessing documents already indexed in dense cells, handling only newly added documents incrementally as they descend through the spatial cell tree.
### Network settings propagation
Settings changes made through individual settings subroutes are now correctly propagated to other remotes in your network.
### Improved Mistral provider compatibility
Fixed an issue where the chat route could fail when using Mistral as a provider.
## Other
### Breaking changes
**Remote federated facet search is now the default**: When using the `network` experimental feature with sharding enabled (`leader` is not `null`), `POST /indexes/{indexUid}/facet-search` calls now default to remote federated search instead of local-only search. The behavior can be controlled via the new `useNetwork` parameter.
**Embedder timeout now tied to search cutoff**: The timeout for calling an external REST embedder at search time is now based on the `searchCutOffMs` setting in the index, rather than using a fixed timeout. If you observe missing semantic results or HTTP 500 errors for pure semantic search after upgrading, increase the value of `searchCutOffMs` in your index settings.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.44.0)
## New Features
### New settings indexer
The new settings indexer provides more efficient handling of index settings modifications. It now supports filterable, sortable, facet search, and custom (asc/desc) attributes in addition to the previously-supported searchable, exact, proximity precision, and embedders.
* For Meilisearch Cloud users, the new settings indexer is disabled by default and can be enabled on a case-by-case basis for scaling purposes.
* For OSS users, the new settings indexer can be disabled by setting the `MEILI_EXPERIMENTAL_NO_EDITION_2024_FOR_SETTINGS` environment variable to `true`.
## Improvements
### Improve facet search indexing performance
Facet search indexing is now faster. The internal data structure generation previously performed multiple full scans on internal entries; it now scans only specific entries dedicated to facet searchable fields.
### Improve task queue compaction integration
The `GET /health` route now returns HTTP 500 after a successful task queue compaction to signal that Meilisearch should be restarted so that tasks can be enqueued again. For Meilisearch Cloud users, this ensures that compacting the task queue will automatically restart the instance after the compaction.
## Other
### Fixed lexicographic filters on strings
Fixed a bug where string facet values used in `<`, `<=`, `>`, `>=`, and `IN` filters were not normalized before comparison to facet values. This caused some values in documents (for example, `2026-01-01T00:00:00`) to appear to have different ordering than expected due to normalization differences (becoming `2026-01-01t00:00:00`).
### Fixed typo tolerance regression
Fixed the `WordDelta::added_or_deleted_words` function that was causing typo tolerance issues introduced in v1.41.
### Security fix in v1.43.1
v1.43.1 contains a fix for an authenticated SSRF vulnerability. Self-hosting users are recommended to upgrade if they allow third parties to configure Meilisearch instances. Meilisearch Cloud users are not required to update.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.43.0)
## New Features
### Remote Availability Status for Query Fallback
The engine now tracks the availability status of remote instances in sharding and replication environments. Each remote is assigned a status (`available` or `unavailable`), allowing the engine to automatically avoid unavailable machines and resume querying them once they're back online.
The `/network` route now exposes remote statuses:
```json theme={null}
{
"remotes": {
"prod2": {
"url": "http://localhost:7702",
"searchApiKey": "mykey",
"writeApiKey": "mykey",
"status": "available"
},
"prod3": {
"url": "http://localhost:7703",
"searchApiKey": "mykey",
"writeApiKey": "mykey",
"status": "unavailable"
}
}
}
```
### Document Join Filtering (Experimental)
Filter documents based on attributes in related indexes using the new `_foreign` filter syntax. This extends cross-index document hydration to allow filtering on foreign indexes during retrieval.
To use this feature, enable the `foreignKeys` experimental feature:
```bash theme={null}
curl -X PATCH 'http://127.0.0.1:7700/experimental-features' \
-H 'Content-Type: application/json' \
--data-binary '{"foreignKeys": true}'
```
Configure foreign keys and filterable attributes in your index settings:
```json theme={null}
{
"foreignKeys": [
{
"fieldName": "actors",
"foreignIndexUid": "actors"
}
],
"filterableAttributes": [
{
"attributePatterns": [
"actors"
],
"features": {
"facetSearch": false,
"filter": {
"equality": true,
"comparison": false
}
}
}
]
}
```
Use the `_foreign` filter in search queries to filter on foreign index attributes:
```json theme={null}
{
"q": "action movies",
"filter": "genres = action AND _foreign(actors, birthDate STARTS WITH \"1958-\" AND popularity >= 3.5)"
}
```
This allows you to find documents based on conditions in related indexes. For example, find movies with a specific genre where actors match certain criteria.
Note: Nesting foreign filters is not supported. This feature does not support remote sharding environments.
## Improvements
### Better Error Handling for Chat Template Updates
The engine now explicitly validates and reports document template errors when updating chat settings, providing clearer feedback on template configuration issues.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.42.0)
## New Features
### Dynamic Search Rules with pinning
Introduce the Dynamic Search Rules (DSR) experimental feature, making it easy to promote the right content for the right searches with flexible, condition-based pinning. In this first version, rules can be triggered by query-based conditions such as empty queries or literal substring matches, as well as time windows. Multiple documents can be pinned in a defined order, and pins still work with filtering, pagination, facet distribution, hybrid search, and federated search.
Rules can be created or updated with `PATCH /dynamic-search-rules/{uid}` and removed with `DELETE /dynamic-search-rules/{uid}`. In this first version, a rule can define query- or time-based conditions and pin specific documents at fixed positions in the results list.
Example of rule creation/update:
```json theme={null}
{
"description": "Promote featured products for wireless headphone searches",
"active": true,
"conditions": [
{
"scope": "query",
"contains": "headphone"
}
],
"actions": [
{
"selector": {
"indexUid": "products",
"id": "featured-headphones-001"
},
"action": {
"type": "pin",
"position": 0
}
},
{
"selector": {
"indexUid": "products",
"id": "featured-headphones-002"
},
"action": {
"type": "pin",
"position": 1
}
}
]
}
```
## Improvements
### Network enabled by default in sharded instances
When `network.leader` is set in the instance, `useNetwork` now defaults to `true` in search requests when omitted. This allows you to naturally query all documents in a sharded context without explicitly requesting network searches.
Search requests now automatically use the network when replicated sharding is enabled, ensuring all shards are covered exactly once. When `network.leader` is not present (particularly when no network is defined), the behavior remains identical to previous versions.
### `useNetwork` optimizes shard selection
To prevent unnecessary network activity, when deciding which remote to ask for a shard in a network search, Meilisearch will now always pick the local instance if it owns the shard.
### More efficient FST building
The construction of the word FST (word dictionary) has been improved by removing the need for a full scan of the word docids database. This drastically improves database performance when inserting a large number of documents, even when inserting only a few.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.41.0)
## New Features
### Support for `distinct` in federated search
The `distinct` attribute can now be passed to the `federation` object in federated search to apply a global, cross-index and cross-remote distinct computation to the results. This works across multiple indexes and remote instances, and supports facet distribution.
Example federated search request with distinct:
```json theme={null}
{
"federation": {
"distinct": "genres",
"facetsByIndex": {
"comics": [
"genres"
],
"movies": [
"genres"
]
},
"mergeFacets": {}
},
"queries": [
{
"indexUid": "comics",
"q": "batman",
"attributesToRetrieve": [
"title",
"genres"
],
"useNetwork": true
},
{
"indexUid": "movies",
"q": "superman",
"attributesToRetrieve": [
"title",
"genres"
],
"useNetwork": true
}
]
}
```
Important notes:
* Applying `distinct` at both the query level and federation level will return an HTTP 400 error
* The distinct field must be a filterable attribute for all participating indexes
* While Meilisearch attempts to compute accurate facet distribution, this cannot be guaranteed in distributed contexts since the distinct algorithm is not applied to all remote documents
### Task queue compaction endpoint
Added `POST /tasks/compact` to compact the task queue database and reclaim space for new tasks without deleting existing tasks. This feature is behind the `taskQueueCompactionRoute` experimental feature flag.
Note: Once task queue compaction completes, all write operations are blocked until the server is restarted.
## Improvements
### Faster federated search performance
Federated search is now approximately 100ms faster for all requests. Additionally, the server will no longer be blocked when processing large numbers of federated search requests.
### Optimized JSON document generation
Performance improvements for handling large documents, especially when requesting only a small subset of fields from large documents.
### Better memory usage for large workloads
Updated to mimalloc v3, which improves memory sharing between threads and significantly reduces memory usage on large workloads. The allocator is now overridden to use mimalloc at linking time, allowing LMDB, Meilisearch, and other C libraries to share allocations for better overall memory efficiency.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.40.0)
## New Features
### Cross-index document hydration with Foreign Keys
A new experimental `foreignKeys` feature allows you to hydrate documents with related documents from other indexes.
First, enable the feature via the experimental features endpoint:
```bash theme={null}
curl -X PATCH 'http://127.0.0.1:7700/experimental-features' \
-H 'Content-Type: application/json' \
--data-binary '{"foreignKeys": true}'
```
Then configure foreign key relationships in your index settings using the `foreignKeys` setting:
```json theme={null}
{
"foreignKeys": [
{
"fieldName": "actors",
"foreignIndexUid": "actors"
}
]
}
```
With this configuration, documents containing foreign document IDs will be automatically hydrated with the full documents from the referenced index. For example, a document like:
```json theme={null}
{
"id": 1,
"title": "Forrest Gump",
"actors": [
1
]
}
```
Will be returned in search results as:
```json theme={null}
{
"id": 1,
"title": "Forrest Gump",
"actors": [
{
"id": 1,
"name": "Tom",
"familyName": "Hanks",
"birthDate": "1956-07-09"
}
]
}
```
Note: This feature does not support remote sharding environments.
## Improvements
### Improved Server-Sent Events (SSE) streaming
Added `X-Accel-Buffering: no` header to the `POST /chats/{workspace_uid}/chat/completions` endpoint when streaming mode is activated. This ensures that proxy response buffering is disabled for real-time streaming chat responses.
### Fixed memory leak in indexation pipeline
Resolved a significant memory leak that has been present since v1.12. If you noticed Meilisearch consuming increasing amounts of memory over time, this issue is now fixed.
### Restored task deletion performance
Fixed a performance regression in v1.38.1 that affected task deletion operations. Task deletion performance has been restored to v1.38.0 levels while maintaining data consistency.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.39.0)
## New Features
## Improvements
Embeddings indexing performance significantly improved by upgrading to the latest version of Hannoy. The indexing process no longer requires full database scans, making it much more efficient to add embeddings to large databases.
Task deletion has been optimized and fixed to properly clean up orphan tasks and batches from the task queue.
Connection reliability improved when using remote embedders like OpenAI or VoyageAI. Fixed intermittent "connection reset by peer" errors that could occur when embedding documents or search queries.
## Other
Routes in the codebase must now be declared using the `routes::routes` and `routes::path` macros to ensure they appear in the API reference documentation. This is now a mandatory requirement for new routes.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.38.0)
## New Features
### Replicated sharding
Replicated sharding is now available in Meilisearch Enterprise Edition, allowing you to configure multiple remotes to own the same shards for data redundancy.
The `network` object now includes a new `shards` field that defines how documents are distributed across remotes:
```json theme={null}
{
"leader": "ms-00",
"self": "ms-01",
"remotes": {
"ms-00": {},
"ms-01": {},
"ms-02": {}
},
"shards": {
"shard-a": {
"remotes": [
"ms-00",
"ms-01"
]
},
"shard-b": {
"remotes": [
"ms-01",
"ms-02"
]
},
"shard-c": {
"remotes": [
"ms-02",
"ms-00"
]
}
}
}
```
Each shard can be owned by multiple remotes, enabling full or partial replication across your network.
#### Managing shards with convenience fields
When updating the network configuration via `PATCH /network`, use `addRemotes` and `removeRemotes` for easier shard management:
```json theme={null}
{
"shards": {
"shard-a": {
"addRemotes": [
"ms-00"
]
}
}
}
```
```json theme={null}
{
"shards": {
"shard-a": {
"removeRemotes": [
"ms-02"
]
}
}
}
```
#### Filtering by shard
When the network feature is enabled, you can now filter documents by their shard assignment using the `_shard` filter:
```text theme={null}
_shard = "shard-a"
_shard != "shard-a"
_shard IN ["shard-a", "shard-b"]
```
This is useful for manual federated search queries across specific shards in your network.
### Shard-aware federated search with `useNetwork`
When you use `useNetwork: true` in search queries, Meilisearch automatically expands the query to ensure each shard in your network configuration is queried exactly once, preventing duplicate or missing results in replicated sharding setups.
## Improvements
### Stabilized new vector store
The hannoy HNSW vector store is now the default and only supported vector store. All existing indexes using the legacy arroy vector store are automatically migrated during upgrade.
### Faster embedding indexing
Vector indexing performance has been significantly improved. On databases with 20M documents, indexing batches of 1100 documents now complete 300 seconds faster.
### Enhanced mini-dashboard security
The local web interface (mini-dashboard) now stores API keys in RAM instead of browser storage, and dependencies with potential security vulnerabilities have been updated.
## Other
### Breaking changes for network feature
If you are using the `network` experimental feature, the following changes apply:
* The `network` object structure has changed. When `leader` is not `null`, you must now include at least one `shard` object with at least one remote in the `shards` field.
* Existing databases are automatically migrated when upgraded with `--experimental-dumpless-upgrade`. The migration creates shards with the same names as existing remotes, mapping each remote to its corresponding shard. This migration does not reshard any documents.
* When updating a network using dumpless upgrade, follow these guidelines:
* Wait for all remotes to finish updating before calling `PATCH /network`
* If using `useNetwork: true` search queries, call them on non-updated remotes first, as updated remotes will reject search requests from remotes that don't yet support the new `_shard` filters
### Removed `vectorStoreSetting` experimental feature
The `vectorStoreSetting` experimental feature has been removed since the new hannoy vector store is now the only supported option.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.37.0)
## New Features
### New Ranking Rules: `attributeRank` and `wordPosition`
Meilisearch now includes two new ranking rules that improve search relevance:
* `attributeRank`: Documents rank higher when query words match in higher-priority searchable attributes, regardless of word position within those attributes.
* `wordPosition`: Documents rank higher when query words appear closer to the beginning of an attribute.
These rules were previously used internally as part of the `attribute` ranking rule. Now you can use them independently for more fine-grained control over search relevance. This is the first significant update to ranking rules since v1.0.
### Automatic Vector Store Migration
When upgrading to v1.36.0, Meilisearch automatically migrates indexes from the old Annoy vector store to the new Hannoy vector store. This migration happens without requiring a data dump and restore, though it may take a couple of minutes for indexes with large numbers of embeddings.
To have more control over the migration timing, you can manually change the vector store backend beforehand by enabling the `vectorStoreSetting` experimental feature and setting the `vectorStore` root setting to `experimental`.
Note: This vector store change affects ranking scores for vector search results.
## Other
### Breaking Change: OpenAPI Documentation File Relocation
The `meilisearch-openapi-mintlify.json` file is no longer included in release assets. If you were using this file, you can now find it in the [public documentation repository](https://github.com/meilisearch/documentation/blob/main/assets/open-api/meilisearch-openapi-mintlify.json).
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.36.0)
## New Features
### Search Performance Observability
All search routes now support a `showPerformanceDetails` parameter that returns detailed performance metrics for your searches. When enabled, the response includes a `performanceDetails` field with timing information for each stage of the search pipeline.
**Affected routes:**
* `POST /indexes//search`
* `GET /indexes//search`
* `POST /multi-search`
* `POST /indexes//similar`
* `GET /indexes//similar`
#### Search Example
Request:
```json theme={null}
{
"q": "glass",
"showPerformanceDetails": true
}
```
Response:
```json theme={null}
{
"hits": ,
"query": "glass",
"processingTimeMs": 5,
"limit": 20,
"offset": 0,
"estimatedTotalHits": 1,
"requestUid": "",
"performanceDetails": {
"wait for permit": "295.29µs",
"search > tokenize": "436.67µs",
"search > resolve universe": "649.00µs",
"search > keyword search": "515.71µs",
"search > format": "288.54µs",
"search": "3.56ms"
}
}
```
#### Multi-search Example
Request:
```json theme={null}
{
"queries": [
{
"indexUid": "",
"q": "glass",
"showPerformanceDetails": true
}
]
}
```
#### Federated Search Example
Request:
```json theme={null}
{
"federation": {
"showPerformanceDetails": true
},
"queries": [
{
"indexUid": "",
"q": "glass"
}
]
}
```
#### Similar Documents Example
Request:
```json theme={null}
{
"id": 143,
"embedder": "manual",
"showPerformanceDetails": true
}
```
## Improvements
### Multithreaded Post-processing Now Always Enabled
Multithreaded post-processing of facets and prefixes is now permanently enabled, removing the experimental feature flag. This results in faster indexing on multi-core machines.
## Other
### Breaking Change: Fields Endpoint Response Format
The `POST /indexes//fields` route now returns a paginated object instead of a direct array. This allows you to see how many fields match a given filter.
Before:
```json theme={null}
[
{}
]
```
After:
```json theme={null}
{
"results": [
{}
],
"offset": 0,
"limit": 20,
"total": 0
}
```
### Fields Endpoint Pattern Filtering Fix
Fixed incorrect pattern matching where parent fields were incorrectly matching child field patterns. For example, a `title` field will no longer match the pattern `title.en`.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.35.0)
## New Features
### Network-wide search with `useNetwork` parameter
You can now search across your entire network of Meilisearch machines with a single request using the new `useNetwork` parameter. This simplifies distributed search by automatically querying all remotes in your network without manually setting up federated queries.
The `useNetwork` parameter is available in three ways:
**In regular search requests:**
```http theme={null}
POST /indexes/{indexUid}/search
```
```json theme={null}
{
"q": "Batman dark knight returns 1",
"filter": "genres IN [Action, Adventure]",
"facets": [
"genres"
],
"useNetwork": true,
"limit": 5
}
```
Or as a query parameter:
```http theme={null}
GET /indexes/{indexUid}/search?useNetwork=true&q=Batman
```
**In multi-search requests:**
Add `useNetwork` to individual queries within a federated search:
```json theme={null}
{
"federation": {
"limit": 5
},
"queries": [
{
"q": "Batman returns",
"indexUid": "movies",
"useNetwork": true
},
{
"q": "Superman returns",
"indexUid": "movies",
"useNetwork": true
}
]
}
```
When `useNetwork: true` is set, Meilisearch automatically queries all remotes in your network and merges the results. The response includes `_federation` metadata showing which remote each result came from. This feature requires the `network` experimental feature to be enabled.
**Limitations:** Facet search and chat routes do not currently support `useNetwork`.
### Federated search pagination
Federated searches now support exhaustive pagination with `federation.page` and `federation.hitsPerPage` parameters, allowing you to paginate through aggregated results from all remotes in the same way as regular searches.
## Improvements
### Faster settings updates when removing searchable attributes
Settings changes are now processed more efficiently when you remove searchable attributes from your index configuration.
## Other
### Security fix: Restrict outbound requests to non-global IP networks
Meilisearch now prevents outbound web requests (webhooks, embedders, and network machine connections) from reaching non-global IP addresses by default. This blocks requests to private networks like `192.168.x.x`, `10.x.x.x`, and localhost, preventing potential firewall bypasses.
**If you need to allow requests to private networks**, use the `--experimental-allowed-ip-networks` CLI flag or `MEILI_EXPERIMENTAL_ALLOWED_IP_NETWORKS` environment variable:
* **Default (not set):** All requests to non-global IPs are blocked
* **Comma-separated CIDR networks:** Allow requests only to specified networks, e.g. `192.168.0.0/16,10.0.0.0/8`
* **`any`:** Allow all requests regardless of target IP (use only in controlled environments)
Example:
```bash theme={null}
meilisearch --experimental-allowed-ip-networks "192.168.0.0/16,10.0.0.0/8"
```
This is a breaking change made for security reasons. Users with API keys that have write permissions to instance configuration could previously configure Meilisearch to send requests to private network addresses, bypassing firewalls.
### Database size increase for authentication
The authentication store database size has been increased to 2 GiB to support indexing more API keys.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.34.0)
## New Features
### Fields endpoint
A new POST `/indexes/{indexUid}/fields` endpoint returns detailed metadata about all fields in an index. This provides comprehensive information about each field's configuration, including display, search, filtering, and localization settings.
## Improvements
### Faster dumpless upgrades
The dumpless upgrade process for instances before v1.32.0 is now significantly faster. By multi-threading database fetches during parallel cleanup of old field IDs, upgrade times have improved from approximately 2 hours 50 minutes to less than 7 minutes.
### Enhanced vector search quality
The vector store has been updated to improve search performance and result quality on larger databases. Linear scanning now triggers more intelligently, particularly when the number of filtered candidates is small relative to the total documents in the index.
### Better ranking with vector search and sorting
Fixed a bug where only the first non-blocking buckets were considered for non-final ranking rules. Search results are now higher quality when vector search and sorting are combined, especially when the search cutoff is triggered.
## Other
### Security fix: Dump import vulnerability
All versions of Meilisearch before v1.33.0 are vulnerable to a path traversal vulnerability in the dump import functionality. Importing a specially crafted dump could grant access to arbitrary files on the file system of the Meilisearch instance.
If you allow importing dumps from untrusted sources, update to v1.33.1 or later. Cloud users require no action as there is no evidence of exploitation on Meilisearch Cloud.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.33.0)
## New Features
### Skip field-ID-based database cleanup during upgrades
Introduces a `MEILI_EXPERIMENTAL_DISABLE_FID_BASED_DATABASES_CLEANUP` environment variable that allows you to opt out of the field ID-based database cleanup when upgrading from Meilisearch versions prior to 1.32.0. This provides flexibility for users who need to control the upgrade process.
## Improvements
### Enhanced search performance visibility with detailed logging
Adds comprehensive progress tracking and logging for search operations, including detailed timing information for each step of the search process. This enables better observability and performance analysis for your search queries.
### Parallel document operation extraction for faster indexing
Accelerates document indexing by processing document operations in parallel during the payload preparation phase. This includes parallel extraction of changes and internal ID assignment. Performance improvements scale with CPU availability - testing shows approximately 7x speedup on four-million-document insertions using four CPUs.
Note: The `indexedDocuments` field in tasks using skipCreation may report higher counts than the actual number of operations for `POST` and `PUT` requests. The documents are indexed correctly; only the reported count may be impacted as speed is prioritized over perfect accuracy in this optimization.
## Other
### Vector sort bucketing fix
Fixed vector sort to properly group documents with identical similarity scores, ensuring subsequent ranking rules are applied correctly to bucketed results.
### Document deletion from field-ID-based databases
Resolved a bug where changing `searchableAttributes` from `["*"]` to a subset of fields left orphaned data in field-ID-based databases, causing corruption and warnings during search operations.
### Graph link rebuilding for dumpless upgrades
Updated hannoy to v0.1.3-nested-rtxns, which fixes graph-related recall issues and adds functionality to rebuild graph links for recovering previously malformed graphs. Also fixed a minor issue in the dumpless upgrade flow where upgrade descriptions were not displayed correctly.
### Fixed panic on dumpless upgrade with empty indexes
Resolved a panic that occurred when performing dumpless upgrades on empty indexes with configured embeddings.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.32.0)
## New Features
### Allow strict document update without creating missing documents
Added an optional `skipCreation` query parameter to document update endpoints. When set to `true` on `POST` or `PUT` requests to `/indexes/{index}/documents`, documents that don't exist in the index are silently ignored rather than created. The default value is `false`, which preserves the existing behavior of creating new documents.
Example usage:
```http theme={null}
POST /indexes/my-index/documents?skipCreation=true
```
## Improvements
### S3-streaming snapshots now available as Enterprise Edition feature
S3-streaming snapshots functionality is now exclusively available in the Enterprise Edition. This requires a license for self-hosted deployments. On-disk snapshots remain available in all editions. If you're using the Community Edition between versions 1.25 and 1.30, you can continue using S3 Streaming without a license.
### AWS IRSA authentication support for S3 snapshots
Added support for AWS IRSA (IAM Roles for Service Accounts) authentication when performing snapshots to S3. This allows the use of short-lived access and secret keys for more secure snapshot uploads. This feature is available in the Enterprise Edition and can be configured through new experimental CLI parameters.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.31.0)
## Improvements
### Network scaling with dynamic topology changes
Meilisearch Enterprise Edition now supports modifying the number of participants in a sharded network without restarting or migrating to a new cluster. You can scale up by adding new remotes or scale down by removing existing ones.
**Setting up the initial network:**
1. Designate a leader machine that will receive all write operations. Any write request to a non-leader machine will return a `not_a_leader` error.
2. Configure your network topology by calling `PATCH /network` on the leader:
```json theme={null}
{
"self": "ms0",
"leader": "ms0",
"remotes": {
"ms0": {
"url": "URL_OF_MS0",
"searchApiKey": "SEARCH_API_KEY_OF_MS0",
"writeApiKey": "WRITE_API_KEY_OF_MS0"
},
"ms1": {
"url": "URL_OF_MS1",
"searchApiKey": "SEARCH_API_KEY_OF_MS1",
"writeApiKey": "WRITE_API_KEY_OF_MS1"
}
}
}
```
3. The network configuration is automatically propagated to all members.
4. Send documents and settings only to the leader, they will be distributed across all network participants with automatic sharding.
**Adding a new remote:**
Call `PATCH /network` on the leader with the new remote's information:
```json theme={null}
{
"remotes": {
"ms2": {
"url": "URL_OF_MS2",
"searchApiKey": "SEARCH_API_KEY_OF_MS2",
"writeApiKey": "WRITE_API_KEY_OF_MS2"
}
}
}
```
A `networkTopologyChange` task will automatically rebalance documents across all remotes, including the new one.
**Removing a remote:**
Call `PATCH /network` on the leader and set the remote to `null`:
```json theme={null}
{
"remotes": {
"ms2": null
}
}
```
A `networkTopologyChange` task will automatically redistribute documents from the removed remote to the remaining participants.
### macOS binary availability restored
The `meilisearch-enterprise-macos-amd64` and `meilisearch-macos-amd64` binaries are now available again after being unavailable in v1.29.
### Improved task handling during index operations
Tasks are now properly attributed during index swaps to prevent cross-index task loss.
### Search stability improvement
Fixed an issue that could cause search requests to fail with an internal error about missing field weights. The system now logs a warning instead of crashing when encountering incomplete field weight mappings.
## Other
### Breaking changes for network sharding users
These changes only affect Enterprise Edition users with automatic sharding enabled (`network.leader` set). Standard feature users are not affected.
**Network object structure changes:**
* The `sharding` boolean field has been removed
* A new `leader` field (optional string) has been added to designate the cluster leader
* A new `version` field (UUID) has been added to track network state
**Write operation restrictions:**
The following routes now return a `not_a_leader` error when called on non-leader machines:
* `POST /indexes`
* `PATCH` or `DELETE /indexes/{indexUid}`
* `POST`, `PUT`, or `DELETE /indexes/{indexUid}/documents`
* `POST /indexes/{indexUid}/documents/delete`
* `POST /indexes/{indexUid}/documents/delete-batch`
* `POST /indexes/{indexUid}/documents/edit`
* `PATCH` or `DELETE /indexes/{indexUid}/settings` and related settings routes
* `PATCH /network` (when changing the leader)
* `POST /swap-indexes`
**PATCH /network response change:**
When a leader is configured, `PATCH /network` now returns a `NetworkTopologyChange` task summary instead of the network object itself.
**Dump import behavior:**
When importing dumps, the `self` and `leader` fields are dropped from the network configuration.
**Network topology change task cancellation:**
`NetworkTopologyChange` tasks can be cancelled. When cancelled, documents that have already been moved remain in their new locations, while the network topology reverts to its previous state.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.30.0)
## Improvements
### New settings indexer supports searchable and exact attributes
The improved settings indexer now handles changes to `searchableAttributes`, `exactAttributes`, `proximityPrecision`, and `embedders` settings. This indexer provides better scalability, near-instant cancellations, and displays indexing progress.
The new indexer is enabled automatically when a settings batch contains only changes to these fields. Any other settings changes will use the legacy indexer. For OSS users, you can disable the new settings indexer by setting the `MEILI_EXPERIMENTAL_NO_EDITION_2024_FOR_SETTINGS` environment variable to `true`.
### New vector store enabled by default for new indexes
Starting with v1.29.0, newly created indexes will automatically use the improved vector store backend introduced in v1.21.0, which provides better performance and relevancy. Existing indexes remain unchanged and continue using their current backend.
### Additional HuggingFace embedder models supported
The `huggingFace` embedder now supports models with XLM Roberta architecture, giving you more options for local CPU and GPU-based embeddings.
## Other
### Build requirement change
The git binary must now be present at build time to populate the `commitSha1` field in the `/version` endpoint response. This change was made to improve build performance.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.29.0)
## New Features
### Better language support for Thai, Khmer, and German
Improved word segmentation for Thai, Khmer, and German languages through an upgrade to Charabia v0.9.9. This provides more accurate text processing and search results for these languages.
### Batch progress traces on metrics route
Batch progress information is now exposed on the metrics route, improving the debugging experience when monitoring indexing operations.
## Improvements
### Separated Community and Enterprise editions
Meilisearch now offers separate binary editions. Community Edition binaries retain their original names and remain under the MIT license. Enterprise Edition binaries are identified by "enterprise" in their names and are available under the BUSL-1.1 license. Docker images for the Enterprise Edition are available in the [`getmeili/meilisearch-enterprise`](https://hub.docker.com/r/getmeili/meilisearch-enterprise) repository.
## Other
### Document sorting fix
Fixed an issue where documents without a sortable attribute were incorrectly handled when using the sort parameter on the `/documents` endpoint. Documents without the sortable attribute are now correctly returned after those that have the attribute.
### Metrics route memory usage fix
Fixed a critical bug in the Prometheus metrics route (`/metrics`) that could cause high memory usage and out-of-memory errors when an instance has too many tasks. If you are using the metrics route, upgrade to v1.28.2 or later, or clean up succeeded or failed tasks using the task management API.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.28.0)
## New Features
None in this release.
## Improvements
### Better error messages for S3 snapshot uploads
Errors that occur during S3 snapshot uploads are now displayed in the task queue, making it easier to debug snapshot upload issues.
### Improved task ingestion performance
The default batch size for batched tasks now defaults to half of the max indexing memory, providing better performance during task ingestion.
## Other
### Fixed embedding operation skipping documents
A bug has been fixed that could cause Meilisearch to skip documents during embedding operations:
* When using a Hugging Face embedder, every `available_parallelism`th document in a batch was ignored
* When using a REST embedder with only one embedding per request, every 40th document in a batch was ignored
To verify if documents in your database have been affected:
1. Enable the `multimodal` experimental feature
2. Search or fetch with filter: `NOT _vectors EXISTS` to find documents without vectors
### Fixed document pagination bug
The `/documents/fetch` endpoint no longer returns duplicated results when paginating through sorted documents.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.27.0)
## New Features
### Custom metadata for document tasks
You can now attach custom metadata to document-related tasks to easily track which documents were processed by Meilisearch. When you create or update documents, add the `customMetadata` query parameter to any supported route:
```bash theme={null}
POST /indexes/{indexUid}/documents?customMetadata=my-metadata-for-the-task
```
The metadata value must be URL-encoded. The custom metadata will appear in task responses from the tasks route and in webhooks.
Supported routes:
* `POST /indexes/{indexUid}/documents`
* `PUT /indexes/{indexUid}/documents`
* `DELETE /indexes/{indexUid}/documents/{documentId}`
* `POST /indexes/{indexUid}/documents/delete-batch`
* `POST /indexes/{indexUid}/documents/delete`
* `POST /indexes/{indexUid}/documents/edit`
* `DELETE /indexes/{indexUid}/documents`
Example task response with metadata:
```json theme={null}
{
"results": [
{
"uid": 37,
"batchUid": 37,
"indexUid": "mieli",
"status": "succeeded",
"type": "documentDeletion",
"canceledBy": null,
"details": {
"deletedDocuments": 31944
},
"error": null,
"duration": "PT0.511099S",
"enqueuedAt": "2025-11-06T16:33:37.816237Z",
"startedAt": "2025-11-06T16:33:37.821591Z",
"finishedAt": "2025-11-06T16:33:38.33269Z",
"customMetadata": "removeall"
}
],
"total": 38,
"limit": 2,
"from": 36,
"next": 35
}
```
### More models for HuggingFace embedder
The HuggingFace embedder now supports models with the `modernBERT` architecture for local CPU or GPU embeddings. This includes models like [Ruri v3](https://huggingface.co/cl-nagoya/ruri-v3-30m) and other `modernBERT` models available on HuggingFace.
## Improvements
### Embedder failure modes (Experimental)
You can now configure how Meilisearch handles embedder-related errors. Choose to ignore:
1. Document template rendering failures
2. Embedder request failures (including missing vectors in `userProvided` embedders)
3. Both types of errors
When errors are ignored, documents without embeddings will not cause the task batch to fail. Use this feature carefully, as ignoring errors makes it harder to detect embedder issues.
To enable this experimental feature:
* **Cloud customers:** Contact support
* **OSS users:** Set the `MEILI_EXPERIMENTAL_CONFIG_EMBEDDER_FAILURE_MODES` environment variable to a comma-separated list of error types to ignore:
* `ignore_document_template_failures`
* `ignore_embedder_failures`
Example:
```bash theme={null}
export MEILI_EXPERIMENTAL_CONFIG_EMBEDDER_FAILURE_MODES=ignore_document_template_failures,ignore_embedder_failures
```
### REST embedder timeout control (Experimental)
You can now configure the timeout duration for REST embedder requests.
To enable this experimental feature:
* **Cloud customers:** Contact support
* **OSS users:** Set the `MEILI_EXPERIMENTAL_REST_EMBEDDER_TIMEOUT_SECONDS` environment variable to a positive integer representing seconds
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.26.0)
## New Features
### Search personalization
Add the ability to dynamically rerank search results using Cohere with a personalized prompt. This experimental feature allows you to customize result ordering based on user preferences and context.
### Upload snapshot tarballs to S3
Add the ability to upload snapshots directly to S3. This experimental feature streams the entire snapshot process and utilizes multipart technology to send chunks of data in parallel, making snapshot uploads more efficient.
## Improvements
### German word segmentation
Improved German text segmentation to skip segmenting unknown words instead of breaking them into bigrams. This ensures that German words not in the dictionary remain intact during indexing.
**Note:** If you have a Meilisearch database containing German words, you must reindex your data manually.
### Chinese text segmentation with numbers and English
Enhanced Chinese text segmentation to prevent splitting of numbers and English words that appear alongside Chinese characters. Numbers and English text are now segmented consistently.
**Note:** If you have a Meilisearch database containing Chinese words, you must reindex your data manually.
## Other
### Breaking change: Authorization header redaction in webhooks
The value of the `Authorization` header is now redacted when getting webhooks or in responses from posting a new webhook or deleting a webhook. Previously, the header value was returned in these responses, which posed a security risk. If you were relying on retrieving the `Authorization` header value through the API, this will no longer be possible.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.25.0)
## New Features
### Search Metadata Header
A new `Meili-Include-Metadata` header is now available on search requests. When included, the response will contain a metadata field with information about each query, including a unique identifier (uid), the `indexUid`, and the index's primary key.
## Improvements
### Vector Store Search Cutoff
Improved the interaction between the vector store and the `searchCutoffMs` parameter when using the `"vectorStore": "experimental"` index setting. This provides better control over search performance and timeout behavior when working with vector-based searches.
### Compaction Behavior
Enhanced compaction interactions with task cancellation, resulting in more reliable behavior when managing background indexing tasks.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.24.0)
## New Features
### Index Compaction Task
A new compaction endpoint is now available for indexes. This task defragments the LMDB environment used by each index, which reduces fragmentation that accumulates over time. Indexes typically experience around 30% fragmentation, and compaction can provide significant performance improvements (2-4x speed-ups) in search and indexation operations. This is achieved by reordering LMDB internal pages and removing scattered free pages throughout the file, relocating content to the beginning for better cache efficiency.
## Improvements
### Parallelized Facet Post-Processing
Facet post-processing during indexation is now multi-threaded. Previously, iterating over index prefixes was done in a single-threaded loop, which was a bottleneck. This redesign delivers 4-6x performance improvements for facet-related operations.
### Request UID Added to Search Routes
Search routes now include the request UID in responses, making it easier to track and correlate requests across your system.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.23.0)
## New Features
### Geojson Filtering Support
A new geo backend has been introduced to store and filter geojson data. You can now:
1. Make the `_geojson` field filterable in your index settings
2. Send documents with a `_geojson` field containing valid [geojson](https://datatracker.ietf.org/doc/html/rfc7946)
3. Filter your documents using the new `_geoPolygon` filter, or continue using the existing `_geoBoundingBox` and `_geoPoints` filters
## Improvements
### Remote Federated Search Timeout Configuration
The timeout for remote federated search has been made configurable. Previously set to a fixed 30 seconds, you can now customize this value by setting the `MEILI_EXPERIMENTAL_REMOTE_SEARCH_TIMEOUT_SECONDS` environment variable to a positive integer. This allows you to better accommodate different search configurations and network conditions.
Note: This configuration is only available via environment variable; no CLI flag or configuration file entry is available at this time.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.22.0)
## New Features
### Vector Store Backend
A new vector store backend is now available for improved performance, especially when using binary quantization. To use it:
1. Enable the `vectorStoreSetting` experimental feature
2. Change the `vectorSetting` index setting to `"experimental"` for the indexes where you want to try the new vector store
### Persian Language Support
Added support for Persian language through an update to the character analysis library.
## Improvements
### Indexing Progress Trace
Fixed an issue where observing the progress trace during indexing could cause parts of the trace to be lost.
## Other
### Dumpless Upgrade Fix
If you encountered a decoding error when upgrading with a `rest` embedder, use the dumpless upgrade to v1.21 to fix this issue.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.21.0)
## Improvements
**Display progress trace in in-progress batches**
In-progress batches now display the `progressTrace` field, giving you better visibility into the execution progress of your batch operations.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.20.0)
## New Features
### Automatically shard documents to scale horizontally
Meilisearch can now automatically distribute documents between multiple instances using the new sharding feature. This allows you to scale Meilisearch horizontally by spreading your data across multiple instances.
**Note:** Sharding is available exclusively in Meilisearch Enterprise Edition (EE). The EE features are governed by the Business Source License 1.1, which allows you to use, test, and develop with sharding for free in non-production environments. Please contact sales before using it in production.
## Improvements
### Enhance hybrid search with filter performance
Hybrid search combined with filters has been optimized. In previous versions, mixing hybrid search with filters could significantly increase search time:
```json theme={null}
{
"q": "hello world",
"limit": 100,
"filter": "tag=science",
"hybrid": {
"semanticRatio": 0.5,
"embedder": "default"
}
}
```
Meilisearch now directly computes semantic distance with filtered candidates when only a few candidates match the filter, instead of searching for the closest embeddings in the vector database. This results in substantially faster search times when combining hybrid search with filters.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.19.0)
## New Features
### Query vector in search response
The search response now includes the `queryVector` when using the `retrieveVectors` parameter, making it easier to understand which vector was used for your search.
### Retrieve vectors from specific embedders
You can now retrieve documents with vectors from specific embedders, giving you more control over which embeddings are returned in search results.
### Rename indexes via API
Indexes can now be renamed using the API, providing a programmatic way to manage your index lifecycle.
## Improvements
Performance and usability improvements to vector handling and index management.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.18.0)
## New Features
**Webhook API support**
A new Webhook API is now available, allowing you to set up webhooks for various events in Meilisearch.
**Chat completions route**
A new chat completions endpoint enables you to turn search queries into conversations. This works with your favorite LLMs and is easy to integrate into your applications.
## Improvements
**STARTS\_WITH filter optimization**
The `STARTS_WITH` filter has been optimized and stabilized for better performance. You no longer need to activate the experimental feature to use this operator.
**OpenAPI file publishing**
The OpenAPI specification file is now published with each release as a release asset for easier integration with tools and SDKs.
## Other
**Chat settings endpoint change**
The chat settings endpoint has changed from `PUT` to `PATCH`. If you have integrations or custom implementations using the old `PUT` method, you'll need to update them to use `PATCH` instead.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.17.0)
## New Features
### Multimodal Embeddings
Index and search images alongside text documents using AI-powered multimodal embedders. This experimental feature allows you to create a common semantic representation for images, texts, and other data types, enabling searches with image queries.
Enable the feature:
```bash theme={null}
curl \
-X PATCH 'MEILISEARCH_URL/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{
"multimodal": true
}'
```
Configure a multimodal embedder (example using VoyageAI):
```bash theme={null}
curl \
-X PATCH 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/embedders' \
-H 'Content-Type: application/json' \
--data-binary '{
"voyage": {
"source": "rest",
"url": "https://api.voyageai.com/v1/multimodalembeddings",
"apiKey": "VOYAGE_API_KEY",
"indexingFragments": {
"text": {
"value": {
"content": [
{
"type": "text",
"text": "A movie titled {{doc.title}} whose description starts with {{doc.overview|truncateWords:20}}."
}
]
}
},
"poster": {
"value": {
"content": [
{
"type": "image_url",
"image_url": "{{doc.poster}}"
}
]
}
}
},
"searchFragments": {
"poster": {
"value": {
"content": [
{
"type": "image_url",
"image_url": "{{media.poster}}"
}
]
}
},
"image": {
"value": {
"content": [
{
"type": "image_base64",
"image_base64": "data:{{media.image.mime}};base64,{{media.image.data}}"
}
]
}
},
"text": {
"value": {
"content": [
{
"type": "text",
"text": "{{q}}"
}
]
}
}
},
"request": {
"inputs": [
"{{fragment}}",
"{{..}}"
],
"model": "voyage-multimodal-3"
},
"response": {
"data": [
{
"embedding": "{{embedding}}"
},
"{{..}}"
]
}
}
}'
```
Search using an image URL:
```bash theme={null}
curl -X POST 'MEILISEARCH_URL/indexes/INDEX_NAME/search' \
-H 'content-type: application/json' \
--data-binary '{
"media": {
"poster": "https://image.tmdb.org/t/p/w500/pgqj7QoBPWFLLKtLEpPmFYFRMgB.jpg"
},
"hybrid": {
"embedder": "voyage"
}
}'
```
Or perform a hybrid text search:
```bash theme={null}
curl -X POST 'MEILISEARCH_URL/indexes/INDEX_NAME/search' \
-H 'content-type: application/json' \
--data-binary '{
"q": "A movie with lightsabers in space",
"hybrid": {
"embedder": "voyage",
"semanticRatio": 0.5
}
}'
```
### Export Route for Data Migration
Transfer documents between Meilisearch instances without creating dumps or snapshots. This is particularly useful when migrating from a local machine to Meilisearch Cloud.
```bash theme={null}
curl \
-X POST 'MEILISEARCH_URL/export' \
-H 'Content-Type: application/json' \
--data-binary '{
"url": "http://localhost:7711"
}'
```
You may optionally supply an API key if the target instance requires authentication:
```json theme={null}
{
"url": "http://localhost:7711",
"apiKey": "target-instance-api-key"
}
```
The export will generate a task that begins migrating data between instances. If the request fails, Meilisearch will retry automatically. You can also cancel an export task manually, though this will only interrupt the task locally.
## Improvements
### Better Nested Wildcard Support
Added support for nested wildcards in `attributes_to_search_on`, allowing more flexible search field configurations.
### Improved Geo Field Extraction
Enhanced the extraction of geographic fields from documents for more accurate geo-based filtering and search.
### CPU Utilization During Dump Import
Dump imports now use all available CPUs for faster processing.
### Live Embedder Error Display
The last embedder error is now displayed live in batches, making it easier to diagnose embedding issues.
### Fallback Instance Option
Added the ability to revert to the old indexer using a fallback instance option for compatibility purposes.
### Filters in Chat Completions
Chat completions now support filters, enabling more precise control over the results used in completions.
### Document Route Sorting
The `/documents` route now supports sorting, giving you more control over how documents are retrieved.
### Read-Only Admin Key for New Databases
New empty databases now automatically create a Read-Only Admin key to prevent accidental writes while investigating your database.
### Edition 2024 Indexer in Dumps
Dumps now use the updated edition 2024 documents indexer for better compatibility.
## Other
### Experimental Features and Configuration Changes
* A fallback instance option is available to revert to the old indexer if needed
* The `--experimental-limit-batched-tasks-total-size` environment variable now works correctly
* The `disableOnNumbers` setting is now properly affected by typo tolerance resets
* New databases include a Read-Only Admin key for safer exploration
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.16.0)
## New Features
### Disable typo tolerance for numbers
Set `typoTolerance.disableOnNumbers` to `true` to disable typo tolerance for numbers:
```bash theme={null}
curl -X POST 'http://localhost:7700/indexes/movies/settings' \
-H 'Content-Type: application/json' \
-d '{
"typoTolerance": {"disableOnNumbers": true}
}'
```
Deactivating typo tolerance on numbers can reduce false positives, such as a query term `2024` returning results that include `2025` and `2004`. It may also improve indexing performance.
### Lexicographic string filters
You can now filter strings lexicographically using comparison operators (`<`, `<=`, `>`, `>=`, `TO`) on string values:
```bash theme={null}
curl -X POST 'http://localhost:7700/indexes/movies/search' \
-H 'Content-Type: application/json' \
-d '{
"filter": "release_date >= '2024-06'"
}'
```
This is particularly useful when filtering human-readable dates.
### Chat with your indexes
Create a chat workspace with the appropriate settings to enable conversational features:
```bash theme={null}
curl -X POST 'http://localhost:7700/chats/my-assistant/settings' \
-H 'Content-Type: application/json' \
-d '{
"source": "openAi",
"apiKey": "sk-abc..."
}'
```
Then use the official OpenAI SDK to chat with your indexes:
```javascript theme={null}
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'http://localhost:7700/chats/my-assistant',
apiKey: 'YOUR_MEILISEARCH_CHAT_API_KEY',
});
const completion = await client.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: 'What is Meilisearch?' }],
stream: true,
});
for await (const chunk of completion) {
console.log(chunk.choices[0]?.delta?.content || '');
}
```
A guide is available for setting up a good chat interface for your indexes.
## Improvements
* Allow cancelling an upgrade to a new Meilisearch version by rolling back all upgraded indexes
* Support EC private key as SSL certificate
* Stop compacting snapshots when passing the relevant CLI option, speeding up snapshot generation
* Add new `batchStrategy` field in the batches stats
* Add log field tracking time spent searching in the vector store
* Improve filterable error messages
* Improve error messages on embeddings dimension mismatch
* Update `/network` URL validation error message format
* Expose the task queue's status size in Prometheus metrics
* Fix `_matchesPosition` length calculation to improve client-side cropping
* Fix `_geo` ranking rule
## Other
* Fix a panic in search that could occur when looking for typos with a search prefix having more than 65k possible hits
* Ensure that passing `MEILI_EXPERIMENTAL_MAX_NUMBER_OF_BATCHED_TASKS` set to 0 results in Meilisearch never processing any tasks
* Forbid value `0` for `maxTotalHits` in index settings
* Allow `documentTemplate`s to use array filters on documents (e.g., `join`)
* Fix searchable attributes database bug where some searchable fields were removed from the searchable databases when removed from `filterableAttributes` setting
* Fix chat route missing base URL and Mistral error handling
* Fix various issues with embedding regeneration
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.15.0)
## New Features
### Granular filterable attribute settings
Control which types of filters you want to enable for each attribute in your documents. Use `PATCH /indexes/INDEX_NAME/settings` to specify filter features like equality, comparison, and facet search on a per-attribute basis:
```json theme={null}
{
"filterableAttributes": [
{
"attributePatterns": [
"genre",
"artist"
],
"features": {
"facetSearch": true,
"filter": {
"equality": true,
"comparison": false
}
}
},
{
"attributePatterns": [
"rank"
],
"features": {
"facetSearch": false,
"filter": {
"equality": true,
"comparison": true
}
}
}
]
}
```
This allows you to further optimize indexing speeds by enabling only the filter features you need for each attribute.
### Composite embedders
Use different embedders at search and indexing time to optimize AI-powered search performance. For example, use a remote embedder during indexing (higher bandwidth) and a local embedder during search queries (lower latency).
To use composite embedders:
1. Enable the feature with the `/experimental-features` route:
```bash theme={null}
curl MEILISEARCH_URL/experimental-features \
-H 'Content-Type: application/json' \
-d '{"compositeEmbedders": true}'
```
2. Create an embedder with `source` set to `"composite"`, defining both `searchEmbedder` and `indexingEmbedder`:
```json theme={null}
{
"embedders": {
"text": {
"source": "composite",
"searchEmbedder": {
"source": "huggingFace",
"model": "baai/bge-base-en-v1.5",
"revision": "a5beb1e3e68b9ab74eb54cfd186867f64f240e1a"
},
"indexingEmbedder": {
"source": "rest",
"url": "https://URL.endpoints.huggingface.cloud",
"apiKey": "hf_XXXXXXX",
"documentTemplate": "Your {{doc.template}}",
"request": {
"inputs": [
"{{text}}",
"{{..}}"
]
},
"response": [
"{{embedding}}",
"{{..}}"
]
}
}
}
}
```
Meilisearch will use the `indexingEmbedder` during indexing and the `searchEmbedder` when responding to search queries.
### Retrieve multiple documents by ID
Fetch multiple documents at once by providing their IDs:
```bash theme={null}
curl -H 'Content-Type: application/json' MEILISEARCH_URL/indexes/INDEX_UID/documents -d '{ "ids": ["cody", "finn", "brandy", "gambit"] }'
```
```json theme={null}
{
"results": [
{
"id": "brandy",
"info": 13765493
},
{
"id": "finn",
"info": 35863
},
{
"id": "cody",
"info": 122263
},
{
"id": "gambit",
"info": 22222
}
],
"offset": 0,
"limit": 20,
"total": 4
}
```
Note: Documents are not returned in the queried order, and non-existent documents are ignored.
## Improvements
### Batch document requests
You can now batch together `/documents` requests using either `PUT` or `POST` methods, improving efficiency when working with multiple documents.
### Enhanced batch progress tracking
The `/batches` route now displays timestamped internal indexing steps, giving you better visibility into the indexing process. Batch progress view has also been extended to include indexing of vectors.
### Exhaustive facet count parameter
The `/facet-search` route now supports an `exhaustiveFacetCount` parameter to retrieve an exact facet count instead of estimates.
### Reduced memory consumption
Arroy (the vector storage component) now uses less RAM, improving overall memory efficiency for vector operations.
### Experimental embedding cache
An experimental feature to cache embeddings during search is now available, potentially improving search performance for repeated queries.
### Armenian character handling
Armenian characters are no longer case-sensitive in searches, improving search accuracy for Armenian language content.
### Optimized reindexing
Searchable attributes no longer trigger reindexing when only their order changes, reducing unnecessary processing.
### Improved task handling
Cancellation tasks can now be accepted even when the disk is full, ensuring better reliability of task management.
## Other
### Breaking behavior change
Enabling `rankingScoreThreshold` no longer causes `_rankingScore` to be miscalculated, fixing a significant issue with ranking score accuracy when using threshold filters.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.14.0)
## New Features
### AI-powered search is now stable and enabled by default
AI-powered search is available to all users by default and no longer requires manual activation. Vector search, semantic search, and hybrid search capabilities are now production-ready.
### Dumpless upgrades
Upgrade to new Meilisearch releases without generating a dump file. Use the `--experimental-dumpless-upgrade` flag when starting Meilisearch after updating the binary:
```bash theme={null}
./meilisearch --experimental-dumpless-upgrade
```
This faster and more efficient process replaces the traditional dump-based upgrade method.
> **Warning**: Meilisearch recommends generating a backup snapshot before upgrading. This is an experimental feature, and failed upgrades may lead to database corruption.
### Remote federated search requests
Query multiple Meilisearch instances simultaneously using the `/multi-search` route. This is particularly useful when handling very large databases.
First, enable the `network` experimental feature:
```bash theme={null}
curl \
-X PATCH 'MEILISEARCH_URL/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{"network": true}'
```
Next, configure your network by setting up one `self` instance and multiple `remotes` using the `/network` endpoint:
```bash theme={null}
curl \
-X PATCH 'MEILISEARCH_URL/network/' \
-H 'Content-Type: application/json' \
--data-binary <<'EOF'
{
"remotes": {
"ms-0": {
"url": "http://ms-1235.example.meilisearch.io",
"searchApiKey": "Ecd1SDDi4pqdJD6qYLxD3y7VZAEb4d9j6LJgt4d6xas"
},
"ms-1": {
"url": "http://ms-4242.example.meilisearch.io",
"searchApiKey": "hrVu-OMcjPGElK7692K7bwriBoGyHXTMvB5NmZkMKqQ"
}
},
"self": "ms-0"
}
EOF
```
Repeat this process with every instance in your network. Do not send the same documents to different instances.
Finally, make a `/multi-search` query with the new `federationOptions.remote` parameter:
```bash theme={null}
curl \
-X PATCH 'MEILISEARCH_URL/multi-search/' \
-H 'Content-Type: application/json' \
--data-binary <<'EOF'
{
"federation": {},
"queries": [
{
"q": "Batman returns dark",
"indexUid": "movies",
"federationOptions": {
"remote": "ms-0"
}
},
{
"q": "Batman returns dark",
"indexUid": "movies",
"federationOptions": {
"remote": "ms-1"
}
}
]
}
EOF
```
## Improvements
### Enhanced monitoring and performance insights
* New `usedDatabaseSize` field on the `/stats` route to track actual database usage
* Embeddings information now exposed on the `/stats` route
* Prometheus metrics added to measure task queue latency
* Faster listing of indexes
* Improved task auto-batching with ability to limit total batch size
### Better error messages
Improved error message when an attribute is not filterable, making it easier to debug search configuration issues.
## Other
### Breaking changes
* `vectorStore` is no longer an accepted value for the `/experimental-features` route
* Ollama URLs must end with either `/api/embed` or `/api/embeddings`
* Error codes have been refined:
* `invalid_embedder` has been split into `invalid_search_embedder` and `invalid_similar_embedder` for search and similar endpoints
* `invalid_hybrid_query` has been renamed to `invalid_search_hybrid_query`
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.13.0)
## New Features
### Significant indexing speed improvements
Meilisearch v1.12 introduces major performance improvements for indexing:
* More than twice as fast for raw document insertion tasks
* More than 4x faster for incrementally updating documents in large databases
* Embeddings generation improved up to 1.5x for some workloads
* Performance is maintained or improved on smaller machines
* Task cancellation is also faster
### New index settings: `facetSearch` and `prefixSearch`
Two new index settings allow you to skip parts of the indexing process for additional speed improvements, though this may impact search experience in some use cases.
**`facetSearch`** toggles facet search for all filterable attributes. Default is `true`:
```bash theme={null}
curl \
-X PUT 'http://localhost:7700/indexes/books/settings/facet-search' \
-H 'Content-Type: application/json' \
--data-binary 'true'
```
**`prefixSearch`** configures prefix search capability. Accepts:
* `"indexingTime"`: enables prefix processing during indexing (default)
* `"disabled"`: deactivates prefix search completely
```bash theme={null}
curl \
-X PUT 'http://localhost:7700/indexes/books/settings/prefix-search' \
-H 'Content-Type: application/json' \
--data-binary 'disabled'
```
When `prefixSearch` is disabled, queries like `he` will no longer match `hello`, but indexing is significantly faster.
### New API route: `/batches`
Query information about task batches with the new `/batches` endpoint.
`GET /batches` returns a list of batch objects with the same query parameters as `GET /tasks`:
```bash theme={null}
curl -X GET 'http://localhost:7700/batches'
```
`GET /batches/:uid` retrieves information about a single batch:
```bash theme={null}
curl -X GET 'http://localhost:7700/batches/BATCH_UID'
```
Batch objects include progress tracking, statistics, and task information:
```json theme={null}
{
"uid": 160,
"progress": {
"steps": [
{
"currentStep": "processing tasks",
"finished": 0,
"total": 2
},
{
"currentStep": "indexing",
"finished": 2,
"total": 3
},
{
"currentStep": "extracting words",
"finished": 3,
"total": 13
},
{
"currentStep": "document",
"finished": 12300,
"total": 19546
}
],
"percentage": 37.986263
},
"details": {
"receivedDocuments": 19547,
"indexedDocuments": null
},
"stats": {
"totalNbTasks": 1,
"status": {
"processing": 1
},
"types": {
"documentAdditionOrUpdate": 1
},
"indexUids": {
"mieli": 1
}
},
"duration": null,
"startedAt": "2024-12-12T09:44:34.124726733Z",
"finishedAt": null
}
```
Task objects now include a `batchUid` field to link tasks to their batch:
```json theme={null}
{
"uid": 154,
"batchUid": 142,
"indexUid": "movies_test2",
"status": "succeeded",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
"indexedDocuments": 1
},
"error": null,
"duration": "PT0.027766819S",
"enqueuedAt": "2024-12-02T14:07:34.974430765Z",
"startedAt": "2024-12-02T14:07:34.99021667Z",
"finishedAt": "2024-12-02T14:07:35.017983489Z"
}
```
## Improvements
### Phrase search with `showMatchesPosition`
Phrase searches with `showMatchesPosition` set to `true` now return a single location for the whole phrase instead of individual term locations.
### Array field match positions
When a query finds matching terms in document fields with array values, Meilisearch now includes an `indices` field in `_matchesPosition` specifying which array elements contain the matches.
### New query parameter for `/tasks`
The `GET /tasks` endpoint now accepts a `reverse` parameter. When set to `true`, tasks are returned in reversed order from oldest to newest.
### New Prometheus metrics
Additional Prometheus metrics have been added for better monitoring and observability.
### Better error messages
Error messages now include the index name for improved clarity when debugging issues.
## Other
### Breaking change: `vectorStore` field distribution
The `vectorStore` field in field distribution no longer contains `_vectors`. The previous value was incorrect, and there is no current use case for the fixed value.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.12.0)
## New Features
### AI-powered search improvements
Meilisearch v1.11 introduces several changes to AI-powered search as part of stabilization efforts:
* **Binary quantization for embeddings**: Enable the new `binaryQuantized` option to convert floating-point embeddings into boolean values. This significantly improves performance and reduces database size (up to 10x reduction and 6x faster indexing) but impacts relevancy. This option cannot be reverted once enabled.
```bash theme={null}
curl \
-X PATCH 'http://localhost:7700/indexes/movies/settings' \
-H 'Content-Type: application/json' \
--data-binary '{
"embedders": {
"image2text": {
"binaryQuantized": true
}
}
}'
```
* **Document template improvements**: The `documentTemplate` field now includes a new `field.is_searchable` property. The default template now filters out empty fields and non-searchable attributes for better embedding quality.
* **New embedder option**: `documentTemplateMaxBytes` allows you to truncate document template text when it exceeds a specified byte limit.
* **Updated default OpenAI model**: The default embedding model is now `text-embedding-3-small` instead of `text-embedding-ada-002`.
### Federated search enhancements
Two new federated search options have been added to support facet queries:
* **`facetsByIndex`**: Request facet distribution and stats for each index separately in federated searches
```json theme={null}
POST /multi-search
{
"federation": {
"limit": 20,
"offset": 0,
"facetsByIndex": {
"movies": ["title", "id"],
"comics": ["title"]
}
},
"queries": [
{
"q": "Batman",
"indexUid": "movies"
},
{
"q": "Batman",
"indexUid": "comics"
}
]
}
```
* **`mergeFacets`**: Merge facet data from multiple indexes into a single result set
```json theme={null}
POST /multi-search
{
"federation": {
"limit": 20,
"offset": 0,
"facetsByIndex": {
"movies": ["title", "id"],
"comics": ["title"]
},
"mergeFacets": {
"maxValuesPerFacet": 10
}
},
"queries": [
{
"q": "Batman",
"indexUid": "movies"
},
{
"q": "Batman",
"indexUid": "comics"
}
]
}
```
### Experimental STARTS WITH filter operator
A new experimental `STARTS WITH` filter operator is available. Enable it through experimental features:
```bash theme={null}
curl \
-X PATCH 'http://localhost:7700/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{
"containsFilter": true
}'
```
Then use it in search filters:
```json theme={null}
{
"filter": "hero STARTS WITH spider"
}
```
### Language support improvements
* Added ISO-639-1 language variants and automatic conversion to ISO-639-3
* New German language tokenizer
* Improved Turkish language support
* Fixed Swedish character normalization so `å`, `ä`, and `ö` are no longer normalized
## Improvements
* Improved error handling when using `query.facets` with federated search (now returns appropriate error instead of silently ignoring the parameter)
* Fixed facet value truncation to correctly apply `maxValuesPerFacet` limits
* Improved task cancellation when vectors are used
* Better timeout handling for embedding requests during search (3s timeout added)
* Added timeouts to read and write operations
* Retry logic added for deserialization failures in remote embedding providers (REST/OpenAI/ollama)
* Improved vector display when no custom vectors were provided
* Updated Rhai to fix errors when updating documents with functions
* Batch failed logs now appear at error level
* Removed forced capitalization in search UI fields
## Other
### Breaking changes
* When performing AI-powered searches, `hybrid.embedder` is now **mandatory** in `GET` and `POST` `/indexes/{:indexUid}/search`
* `hybrid` must now be passed even for pure semantic searches
* `embedder` is now **mandatory** in `GET` and `POST` `/indexes/{:indexUid}/similar`
* `semanticRatio` is ignored for queries that include `vector` but not `q` (performs pure semantic search instead)
* When using federated search, `query.facets` at the query level now returns an error instead of being silently ignored. Use `federation.facetsByIndex` instead.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.11.0)
## New Features
### Federated search
Use the new `federation` setting of the `/multi-search` route to return a single search result object combining results from multiple indexes:
```bash theme={null}
curl \
-X POST 'http://localhost:7700/multi-search' \
-H 'Content-Type: application/json' \
--data-binary << 'EOF'
{
"federation": {
"offset": 5,
"limit": 10
},
"queries": [
{
"q": "Batman",
"indexUid": "movies"
},
{
"q": "Batman",
"indexUid": "comics"
}
]
}
EOF
```
Response includes results merged in descending ranking score order with federation metadata:
```json theme={null}
{
"hits": [
{
"id": 42,
"title": "Batman returns",
"overview": "..",
"_federation": {
"indexUid": "movies",
"queriesPosition": 0
}
}
],
"processingTimeMs": 0,
"limit": 20,
"offset": 0,
"estimatedTotalHits": 2,
"semanticHitCount": 0
}
```
Control the relevancy weight of each index using `federationOptions` in each query:
```bash theme={null}
curl \
-X POST 'http://localhost:7700/multi-search' \
-H 'Content-Type: application/json' \
--data-binary << 'EOF'
{
"federation": {},
"queries": [
{
"q": "apple red",
"indexUid": "fruits",
"federationOptions": {
"weight": 3.0
}
},
{
"q": "apple red",
"indexUid": "fruits",
"federationOptions": {
"weight": 0.5
}
}
]
}
EOF
```
The `weight` parameter controls how likely results from each index appear in the final results. Values less than 1.0 make results less likely to appear, while values greater than 1.0 make them more likely. Default is 1.0.
### Language settings
Explicitly define which languages are used in your documents for better search accuracy, particularly helpful for datasets with multiple languages or those that previously required workarounds.
Set languages during indexing with `localizedAttributes`:
```bash theme={null}
curl \
-X PATCH 'http://localhost:7700/indexes/movies/settings' \
-H 'Content-Type: application/json' \
--data-binary << 'EOF'
{
"localizedAttributes": [
{
"locales": ["jpn"],
"attributePatterns": ["*_ja"]
},
{
"locales": ["eng"],
"attributePatterns": ["*_en"]
},
{
"locales": ["cmn"],
"attributePatterns": ["*_zh"]
},
{
"locales": ["fra", "ita"],
"attributePatterns": ["latin.*"]
},
{
"locales": ,
"attributePatterns": ["*"]
}
]
}
EOF
```
Supported language codes include: `epo`, `eng`, `rus`, `cmn`, `spa`, `por`, `ita`, `ben`, `fra`, `deu`, `ukr`, `kat`, `ara`, `hin`, `jpn`, `heb`, `yid`, `pol`, `amh`, `jav`, `kor`, `nob`, `dan`, `swe`, `fin`, `tur`, `nld`, `hun`, `ces`, `ell`, `bul`, `bel`, `mar`, `kan`, `ron`, `slv`, `hrv`, `srp`, `mkd`, `lit`, `lav`, `est`, `tam`, `vie`, `urd`, `tha`, `guj`, `uzb`, `pan`, `aze`, `ind`, `tel`, `pes`, `mal`, `ori`, `mya`, `nep`, `sin`, `khm`, `tuk`, `aka`, `zul`, `sna`, `afr`, `lat`, `slk`, `cat`, `tgl`, `hye`.
Set language at search time with the `locales` parameter:
```bash theme={null}
curl \
-X POST http://localhost:7700/indexes/movies/search \
-H 'Content-Type: application/json' \
--data-binary '{"q": "進撃の巨人", "locales": ["jpn"]}'
```
### Experimental: CONTAINS filter operator
Enable the `containsFilter` experimental feature to filter results containing partial string matches:
```bash theme={null}
curl \
-X PATCH 'http://localhost:7700/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{"containsFilter": true}'
```
Use the `CONTAINS` operator in filter expressions:
```bash theme={null}
curl \
-X POST http://localhost:7700/indexes/movies/search \
-H 'Content-Type: application/json' \
--data-binary '{"q": "super hero", "filter": "synopsis CONTAINS spider"}'
```
### Experimental: Edit documents with a Rhai function
Update a subset of your documents using a function directly from Meilisearch without needing to fetch, modify, and reindex them.
First, enable the experimental feature:
```bash theme={null}
curl \
-X PATCH 'http://localhost:7700/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{"editDocumentsByFunction": true}'
```
Then use the `/documents/edit` route:
```bash theme={null}
curl http://localhost:7700/indexes/movies/documents/edit \
-H 'content-type: application/json' \
--data-binary '{"function": "doc.title = `✨ ${doc.title.to_upper} ✨`", "filter": "id > 3000"}'
```
The `function` parameter accepts [Rhai](https://rhai.rs/book/) code that can modify document fields. Use the `filter` parameter to target specific documents and `context` to pass data to your function.
## Improvements
### Search performance
Implemented intersection at the end of the search pipeline for faster search operations.
### Indexing performance
Stopped opening indexes just to check if they exist, reducing unnecessary overhead during indexing operations.
### AI-powered search enhancements
Several quality-of-life improvements for REST embedders and remote embedding services:
* Add custom headers to REST embedders using the optional `headers` parameter to include additional headers in requests to remote embedders
* Add optional `url` parameter to OpenAI embedder to specify a custom embedding endpoint
* `dimensions` parameter now available for Ollama embedders
* Improved error messages when embeddings are missing or model configurations cannot be loaded
* Exponential backoff duration is now randomized when REST embedder requests fail
* OpenAI embeddings that exceed max tokens are now truncated rather than embedded by chunk
### Error handling and messaging
* Improved tenant token error messages for better debugging
* Wrong HTTP status and confusing error messages on incorrect payloads have been fixed
* Errors at the main Meilisearch binary level are now logged with `ERROR` level for better visibility
### Improved documentation of natural language processing
Added null byte as hard context separator and included all math symbols in the default separator list for better text processing across languages.
### Heavy load handling
* Optimized search queue handling to spawn only one search queue in actix-web
* Improved index scheduler reliability to prevent stopping during heavy loads
* Explicitly drop search permits to free resources more efficiently
* Stop processing searches that take longer than one minute to prevent resource exhaustion
### Document operations
Made autobatching of document deletions with document deletions by filter possible, unclogging the task queue for users performing these operations heavily.
### Search configuration
Added experimental CLI flags to fine-tune search behavior:
* `--experimental-nb-searches-per-core`: Configure how many searches Meilisearch can process concurrently per core
* `--experimental-drop-search-after`: Set how many seconds before Meilisearch considers a search irrelevant and drops it without processing
## Other
### Breaking changes
#### REST embedder configuration
The REST embedder configuration has been simplified and changed in v1.10:
Old v1.9 format:
```json theme={null}
{
"source": "rest",
"url": "https://localhost:10006",
"query": {
"model": "minillm"
},
"inputField": [
"prompt"
],
"inputType": "text",
"embeddingObject": [
"embedding"
]
}
```
New v1.10 format:
```json theme={null}
{
"source": "rest",
"url": "https://localhost:10006",
"request": {
"model": "minillm",
"prompt": "{{text}}"
},
"response": {
"embedding": "{{embedding}}"
}
}
```
The `request` object represents the request sent to the remote embedder, with `{{text}}` as a placeholder for the text to embed. The `response` object represents the response structure, with `{{embedding}}` as a placeholder for the embedding vector.
If you have dumps with REST embedder configurations from v1.9, you must remove embedders with source `"rest"` before importing into v1.10. Attempting to import will result in an error about unknown fields.
### Minimum Ubuntu version requirement
Meilisearch now requires Ubuntu 20.04 or later. Ubuntu 18.04 is no longer supported due to GitHub Actions runner requirements.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.10.0)
## New Features
### Hybrid search updates
Meilisearch v1.9 introduces multiple enhancements to hybrid search functionality:
* The `_vectors` field now accepts object values in addition to embedding arrays, allowing you to specify embeddings with additional metadata:
```json theme={null}
{
"id": 42,
"_vectors": {
"default": [
0.1,
0.2
],
"text": {
"embeddings": [
[
0.1,
0.2,
0.3
],
[
0.4,
0.5,
0.6
]
],
"regenerate": false
},
"translation": {
"embeddings": [
0.1,
0.2,
0.3,
0.4
],
"regenerate": true
}
}
}
```
The `embeddings` field replaces a document's embeddings, while `regenerate` controls whether embeddings are regenerated on future document updates. Set `regenerate: true` to import embeddings as a one-shot process, or `regenerate: false` to preserve embeddings through document updates.
* Use the new `retrieveVectors` search parameter to include the `_vectors` field in search results (performance improvement makes this opt-in by default):
```bash theme={null}
curl \
-X POST 'http://localhost:7700/indexes/INDEX_NAME/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "SEARCH QUERY",
"retrieveVectors": true
}'
```
### Ranking score threshold
Filter search results by minimum quality using the `rankingScoreThreshold` parameter:
```bash theme={null}
curl \
-X POST 'http://localhost:7700/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "Badman dark returns 1",
"showRankingScore": true,
"limit": 5,
"rankingScoreThreshold": 0.2
}'
```
Documents below the threshold are excluded from results and do not count towards `estimatedTotalHits` or `totalHits`.
### Get similar documents endpoint
Find documents similar to a given document using the new `/indexes/{indexUid}/similar` endpoint:
```bash theme={null}
curl \
-X POST 'http://localhost:7700/indexes/:indexUid/similar' \
-H 'Content-Type: application/json' \
--data-binary '{
"id": "23",
"offset": 0,
"limit": 2,
"filter": "release_date > 1521763199",
"embedder": "default",
"attributesToRetrieve": ["*"],
"showRankingScore": false,
"showRankingScoreDetails": false
}'
```
Parameters:
* `id`: Document ID to find similar results for (required)
* `offset`: Number of results to skip (optional, defaults to `0`)
* `limit`: Number of results to return (optional, defaults to `20`)
* `filter`: Filter expression to apply to results (optional)
* `embedder`: Embedder to use for similarity matching (optional, defaults to `"default"`)
* `attributesToRetrieve`: Fields to include in results (optional, defaults to all)
* `showRankingScore`: Include ranking scores (optional, defaults to `false`)
* `showRankingScoreDetails`: Include detailed ranking scores (optional, defaults to `false`)
* `rankingScoreThreshold`: Minimum ranking score threshold (optional)
Supports both `GET` (URL parameters) and `POST` (request body) routes.
### `frequency` matching strategy
Prioritize results containing less frequent query terms using the new `frequency` matching strategy:
```bash theme={null}
curl \
-X POST 'http://localhost:7700/indexes/{index_uid}/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "cheval blanc",
"matchingStrategy": "frequency"
}'
```
### Set distinct attribute at search time
Specify the distinct attribute for a search without modifying index settings using the `distinct` parameter:
```bash theme={null}
curl \
-X POST 'http://localhost:7700/indexes/{index_uid}/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "kefir le double poney",
"distinct": "book.isbn"
}'
```
A search-time `distinct` attribute takes precedence over the index settings.
## Improvements
### Indexing performance
Settings updates are now significantly faster with reduced disk usage. When changing embedding settings, only embedders with modified settings regenerate their embeddings. When only the `documentTemplate` is modified, embeddings regenerate only for documents where the modification affects the text to embed.
### Search performance
* Filter AND operations are now faster during search
* Facet distribution calculations are optimized for improved performance
### Language support
* Added new normalizer to normalize œ to oe and æ to ae
* Fixed `chinese-normalization-pinyin` feature flag compilation
### Relevancy improvements
All fields now have the same impact on relevancy when `searchableAttributes: ["*"]`. Fixed `searchableAttributes` behavior when handling nested fields.
### Prometheus metrics (experimental)
Use HTTP path patterns instead of full paths in metrics for better grouping and analysis.
## Other
### Breaking changes
* Empty `_vectors.embedder` arrays are now interpreted as having no vector embedding (previously interpreted as a single embedding of dimension 0)
* The `_vectors` field is no longer included in search results by default when the experimental `vectorStore` feature is enabled (use `retrieveVectors: true` to opt-in)
* Meilisearch no longer preserves the exact representation of embeddings in `_vectors`. Vectors are stored in a canonicalized float representation (e.g., `3` may be represented as `3.0`)
### Deprecations
The `exportPuffinReport` experimental feature has been removed. Use logs routes and logs modes instead.
### Bug fixes
* Fixed security issue in Rustls dependency
* Fixed embedding settings reset when changing the `source` of an embedder, preventing misleading error messages
* Fixed panic in hybrid search when removing all embedders
* Fixed hybrid search to respect `offset` and `limit` parameters when returning keyword results early
* Fixed issue where dumps with user-provided embedders and documents opting out of vectors would fail to import correctly (v1.9.1)
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.9.0)
## New Features
### Hybrid search enhancements
Meilisearch now supports two new embedder sources for hybrid search:
**Ollama model** - Run language models locally using the Ollama framework:
```json theme={null}
{
"default": {
"source": "ollama",
"url": "http://localhost:11434/api/embeddings",
"apiKey": "",
"model": "nomic-embed-text",
"documentTemplate": "A document titled '{{doc.title}}' whose description starts with {{doc.overview|truncatewords: 20}}"
}
}
```
**Generic REST embedder** - Connect to any embedder with a RESTful interface:
```json theme={null}
{
"default": {
"source": "rest",
"url": "http://localhost:12345/api/v1/embed",
"apiKey": "187HFLDH97CNHN",
"dimensions": 512,
"documentTemplate": "A document titled '{{doc.title}}' whose description starts with {{doc.overview|truncatewords: 20}}",
"inputField": [
"data",
"text"
],
"inputType": "text",
"query": {
"model": "MODEL_NAME",
"dimensions": 512
},
"pathToEmbeddings": [
"data"
],
"embeddingObject": [
"embedding"
]
}
}
```
**Distribution setting** - Apply affine transformations to semantic search ranking scores to improve result ranking when combining semantic and keyword search:
```json theme={null}
{
"default": {
"source": "huggingFace",
"model": "MODEL_NAME",
"distribution": {
"mean": 0.7,
"sigma": 0.3
}
}
}
```
### Negative keywords
Exclude specific terms from search results using the `-` operator:
```bash theme={null}
curl \
-X POST 'http://localhost:7700/indexes/places/search' \
-H 'Content-Type: application/json' \
--data-binary '{"q": "-escape room"}'
```
* `-escape` returns documents that do not contain "escape"
* `-escape room` returns documents containing "room" but not "escape"
* `-"on demand"` returns documents that do not contain the phrase "on demand"
### Search cutoff timeout
Configure a timeout for search requests to prevent crashes and performance issues. Set a custom timeout value using the `/settings` endpoint:
```bash theme={null}
curl \
-X PATCH 'http://localhost:7700/indexes/movies/settings' \
-H 'Content-Type: application/json' \
--data-binary '{"searchCutoffMs": 150}'
```
The default timeout is 1500ms. Set to `null` to disable the cutoff.
## Improvements
### Indexing performance
Increased indexing speed when updating settings.
### Search stability
Added a limit for concurrent search requests to prevent unbounded RAM consumption. Launch your instance with a custom limit:
```bash theme={null}
./meilisearch --experimental-search-queue-size 100
```
The default limit is 1000 enqueued requests. This limit does not impact search performance, only prevents security issues from excessive queueing.
### Facet sorting
The `sortFacetValuesBy` setting now impacts the `/facet-search` route for consistent facet value ordering.
### Hybrid search improvements
* Return keyword search results even if embedding generation fails during hybrid searches
* Added `semanticHitCount` field to search responses indicating the number of hits from semantic search
* Improved search logs to exclude `hits` from DEBUG log level output
### Tokenizer improvements
Enhanced tokenization with support for:
* Markdown formatted code blocks
* Improved Korean segmentation
* Tab character (`\t`) recognition as a separator
* Optional pinyin normalization for Chinese text
### Vector embeddings in dumps
Vectors are now included in database dumps, providing an upgrade path to future versions without requiring regeneration of embeddings for auto-generating embedders.
## Other
### Breaking changes: Semantic search scoring
To improve search response times and reduce bandwidth:
* `_semanticScore` is no longer returned in search responses; use `_rankingScore` instead
* The `vector` field is no longer included in search responses
* Query vectors are no longer displayed when `"showRankingScoreDetails": true` is set
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.8.0)
## New Features
### New OpenAI embedding models
When configuring OpenAI embedders, you can now specify two new models:
* `text-embedding-3-small` with a default dimension of 1536
* `text-embedding-3-large` with a default dimension of 3072
These new models are cheaper and improve search result relevancy.
### Custom OpenAI model dimensions
You can configure `dimensions` for sources using the new OpenAI models: `text-embedding-3-small` and `text-embedding-3-large`. Dimensions must be greater than 0 and smaller than the model size:
```json theme={null}
{
"embedders": {
"new_model": {
"source": "openAi",
"model": "text-embedding-3-large",
"dimensions": 512
},
"legacy_model": {
"source": "openAi",
"model": "text-embedding-ada-002"
}
}
}
```
You cannot customize dimensions for older OpenAI models such as `text-embedding-ada-002`. Setting `dimensions` to any value except the default size of these models will result in an error.
### GPU support for Hugging Face embeddings
Activate CUDA to use Nvidia GPUs when computing Hugging Face embeddings. This can significantly improve embedding generation speeds.
To enable GPU support through CUDA:
1. Install CUDA dependencies
2. Clone and compile Meilisearch with the `cuda` feature: `cargo build --release --package meilisearch --features cuda`
3. Launch your freshly compiled Meilisearch binary
4. Activate vector search
5. Add a Hugging Face embedder
### Stabilized `showRankingScoreDetails`
The `showRankingScoreDetails` search parameter is now a stable feature. Use it with the `/search` endpoint to view detailed scores per ranking rule for each returned document:
```bash theme={null}
curl \
-X POST 'http://localhost:7700/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{"q": "Batman Returns", "showRankingScoreDetails": true}'
```
When `showRankingScoreDetails` is set to `true`, returned documents include a `_rankingScoreDetails` field with detailed scoring information for each ranking rule.
### Experimental JSON log output
Configure Meilisearch to output logs in JSON format by passing `json` to the `--experimental-logs-mode` command-line option:
```bash theme={null}
./meilisearch --experimental-logs-mode json
```
The `--experimental-logs-mode` option accepts two values:
* `human`: default human-readable output
* `json`: JSON structured logs
### Experimental `/logs/stream` and `/logs/stderr` routes
Two new experimental API routes allow you to manage log output:
**Activate the routes** using the `/experimental-features` endpoint:
```bash theme={null}
curl \
-X PATCH 'http://localhost:7700/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{"logsRoute": true}'
```
**`/logs/stream`** - Stream logs in real-time:
```bash theme={null}
curl \
-X POST http://localhost:7700/logs/stream \
-H 'Content-Type: application/json' \
--data-binary '{"mode": "human", "target": "actix=off,debug"}'
```
Parameters:
* `target`: Defines log level and which part of the engine to apply it to. Format: `code_part=log_level`. Valid log levels: `trace`, `debug`, `info`, `warn`, `error`, or `off`
* `mode`: Accepts `fmt` (basic) or `profile` (verbose trace)
Stop streaming with:
```bash theme={null}
curl -X DELETE http://localhost:7700/logs/stream
```
You may only have one listener at a time.
**`/logs/stderr`** - Configure default log output:
```bash theme={null}
curl \
-X POST http://localhost:7700/logs/stderr \
-H 'Content-Type: application/json' \
--data-binary '{"target": "debug"}'
```
Parameters:
* `target`: Defines log level and which part of the engine to apply it to. Format: `code_part=log_level`. Valid log levels: `trace`, `debug`, `info`, `warn`, `error`, or `off`
### Experimental cluster mode
New experimental feature to change the behavior of Meilisearch to run in a cluster by externalizing the task queue.
## Improvements
### Improved indexing speed and reduced memory usage
* Auto-batch task deletion reduces indexing time
* Hybrid search experimental feature indexing is now more than 10 times faster
* Capped the maximum memory of grenade sorters to reduce memory usage
* Multiple technical improvements to indexing pipeline
* Enhanced facet incremental indexing
* Improved threshold triggering incremental indexing
### Improved logging
Log messages now follow a new pattern:
```text theme={null}
2024-02-06T14:54:11Z INFO actix_server::builder: 200: starting 10 workers
```
This replaces the previous format:
```text theme={null}
[2024-02-06T14:54:11Z INFO actix_server::builder] starting 10 workers
```
### Multiple language support improvements
Expanded support for multiple languages, including improved Vietnamese normalization (Ð and Đ are now normalized to d). Updated to Charabia v0.8.7.
### Additional improvements
* Added content type to webhook requests
* Skip reindexing when modifying unknown faceted fields
* Added timeout to webhook requests
* Enhanced Prometheus experimental feature with job variable in Grafana dashboard
## Other
### Breaking changes - Log output format
Log messages now follow a different pattern. If you have automated tasks based on log output parsing, you may need to update them to work with the new format.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.7.0)
## New Features
### Automated embeddings generation for vector search
Meilisearch can now automatically generate embeddings using OpenAI, HuggingFace, or your own pre-computed vectors. Configure embedders in your index settings:
```bash theme={null}
curl \
-X PATCH 'http://localhost:7700/indexes/movies/settings' \
-H 'Content-Type: application/json' \
--data-binary << 'EOF'
{
"embedders": {
"default": {
"source": "openAi",
"apiKey": "",
"model": "text-embedding-ada-002",
"documentTemplate": "A movie titled '{{doc.title}}' whose description starts with {{doc.overview|truncatewords: 20}}"
},
"image": {
"source": "userProvided",
"dimensions": 512
},
"translation": {
"source": "huggingFace",
"model": "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
"documentTemplate": "A movie titled '{{doc.title}}' whose description starts with {{doc.overview|truncatewords: 20}}"
}
}
}
EOF
```
The `documentTemplate` field uses Liquid format to define what content gets embedded. The `model` parameter specifies which OpenAI or HuggingFace model to use.
### Hybrid search
Combine keyword and semantic search in a single query using the new `hybrid` parameter:
```bash theme={null}
curl \
-X POST 'http://localhost:7700/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "Plumbers and dinosaurs",
"hybrid": {
"semanticRatio": 0.9,
"embedder": "default"
}
}'
```
The `semanticRatio` controls the balance between semantic and keyword search (0 = pure keyword search, 1 = pure semantic search, default = 0.5).
### Task queue webhook
Receive notifications when Meilisearch finishes processing tasks by configuring a webhook:
```bash theme={null}
./meilisearch \
--task-webhook-url=https://example.com/example-webhook?foo=bar&number=8 \
--task-webhook-authorization-header=Bearer aSampleAPISearchKey
```
You can also set these via `MEILI_TASK_WEBHOOK_URL` and `MEILI_TASK_WEBHOOK_AUTHORIZATION_HEADER` environment variables or in your configuration file.
### Experimental: Limit batched tasks
Control how many tasks Meilisearch batches together to improve system stability:
```bash theme={null}
./meilisearch --experimental-max-number-of-batched-tasks 100
```
Configure via `MEILI_EXPERIMENTAL_MAX_NUMBER_OF_BATCHED_TASKS` environment variable or in your configuration file.
## Improvements
### Indexing performance
Meilisearch v1.6 significantly improves indexing speed by storing less internal data and only re-indexing the specific fields you update. On a 2.5GB e-commerce dataset, initial document addition is over 50% faster. Partial document updates show 50-75% performance improvements depending on your dataset and indexing patterns.
### Disk space reduction
Database disk usage is now 40-50% smaller on typical datasets due to reduced internal data storage. Database growth is also more stable with new document additions.
### Proximity ranking rule precision
Control the accuracy and performance of proximity-based ranking:
```bash theme={null}
curl \
-X PATCH 'http://localhost:7700/indexes/books/settings/proximity-precision' \
-H 'Content-Type: application/json' \
--data-binary '{
"proximityPrecision": "byAttribute"
}'
```
Choose between `byWord` (exact distance, default) or `byAttribute` (faster but less precise, checks only if words appear in the same field).
## Other
### Vector search breaking changes
If you've used vector search in v1.3.0 through v1.5.0, update your implementation:
* When using both `q` and `vector` parameters together, you must now include the `hybrid` parameter
* Vectors must be JSON objects instead of arrays:
```json theme={null}
{
"_vectors": {
"image2text": [
0,
0.1,
0.2
]
}
}
```
* Define a model in your embedder settings (previously optional for user-provided embeddings):
```json theme={null}
{
"embedders": {
"default": {
"source": "userProvided",
"dimensions": 512
}
}
}
```
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.6.0)
## New Features
### Snapshots on-demand
A new `/snapshots` API route allows you to create snapshots manually whenever needed:
```bash theme={null}
curl -X POST http://localhost:7700/snapshots
```
This route returns a summarized task object. By default, snapshots are created in the `/snapshots` directory, which you can customize using the `--snapshot-dir` configuration option.
### Experimental feature: Export Puffin reports
Meilisearch can now automatically export `.puffin` reports to help diagnose performance issues. Enable this experimental feature using the `/experimental-features` endpoint:
```bash theme={null}
curl \
-X PATCH 'http://localhost:7700/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{"exportPuffinReports": true}'
```
## Improvements
### Indexing speed improvements
Indexing speed has been improved for text-heavy datasets. Datasets with fields containing more than 100 words should see a 5% to 20% reduction in indexing times. Gains are proportional to the amount of words in a document. Note: indexing speed improvements may not be visible in datasets with fewer than 20 words per field.
Please be aware that this optimization might result in minor impact to search result relevancy for queries containing 4 words or more. Contact the Meilisearch team if this significantly affects your application.
### Additional improvements
* The experimental `/metrics` route can now be activated via HTTP in addition to CLI flags
* Added Khmer language support
* The `meilitool` command-line interface is now integrated into the Meilisearch Docker image, providing commands to enforce task cancellation and dump creation for stuck instances. Use `meilitool --help` in the running container for usage information
## Other
### Breaking changes and fixes
* Vector size validation: The API now throws an error when a vector in a search query does not match the size of already indexed vectors
* Fixed search operations on the processing index from hanging
* Fixed search on exact attributes using `attributeToSearchOn`
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.5.0)
## New Features
### Customize text separators
Meilisearch word segmentation now supports customization through two new index settings: `separatorTokens` and `nonSeparatorTokens`.
Add a character to the `separatorTokens` list to use it as a word separator:
```bash theme={null}
curl \
-X PUT 'http://localhost:7700/indexes/articles/settings/separator-tokens' \
-H 'Content-Type: application/json' \
--data-binary '["§", "&sep"]'
```
Add a character to the `nonSeparatorTokens` list when you don't want Meilisearch to use it to separate words:
```bash theme={null}
curl \
-X PUT 'http://localhost:7700/indexes/articles/settings/non-separator-tokens' \
-H 'Content-Type: application/json' \
--data-binary '["@", "#", "&"]'
```
### Load user-defined dictionaries
Expand Meilisearch's default language-based dictionaries with domain-specific terms using the new `dictionary` index setting. This improves word segmentation accuracy for specialized vocabularies:
```bash theme={null}
curl \
-X PUT 'http://localhost:7700/indexes/articles/settings/dictionary' \
-H 'Content-Type: application/json' \
--data-binary '["J. R. R.", "J.R.R."]'
```
The `dictionary` setting works alongside existing `stopWords` and `synonyms` settings:
```json theme={null}
{
"dictionary": [
"J. R. R.",
"J.R.R."
],
"synonyms": {
"J.R.R.": [
"jrr",
"J. R. R."
],
"J. R. R.": [
"jrr",
"J.R.R."
],
"jrr": [
"J.R.R.",
"J. R. R."
]
}
}
```
## Improvements
### Enhanced data privacy in error messages
Hidden document fields are no longer displayed in error messages. When attempting to sort by a non-sortable field while other non-displayed sortable fields exist, you'll see a message like:
```text theme={null}
Available sortable attributes are: price, stock, <..hidden-attributes>.
```
### Improved filter parameter handling with backslashes
Fixed a bug preventing proper use of backslash characters in `filter` search parameter expressions. This change requires updating how backslashes are escaped in filters.
If you use backslashes in filter expressions, you must now escape them. For example:
* Before v1.4.0: `path = "my\\test\\path"`
* From v1.4.0: `path = "my\\\\test\\\\path"`
The JSON layer unescapes `\\\\` to `\\`, and then Meilisearch unescapes `\\` to a single `\`.
### Search performance improvements
Improved indexing speed when importing dumps by using buffered readers and writers.
## Other
### Breaking change: Backslash escaping in filter expressions
Users with backslash characters in `filter` search parameters must update their filter expressions. All backslashes now require escaping at the Meilisearch filter level (in addition to any JSON escaping).
For a document with `path: "my\test\path"` stored as `"my\\test\\path"` in JSON, the filter syntax changed:
* Previously: `path = "my\\test\\path"` (only JSON escaping)
* Now: `path = "my\\\\test\\\\path"` (JSON + Meilisearch filter escaping)
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.4.0)
## New Features
### Vector Search (Experimental)
Meilisearch now supports vector search, allowing you to use it as a vector store. You can add vector embeddings generated by third-party tools (such as Hugging Face, Cohere, or OpenAI) and search using vector similarity.
Enable vector search via the experimental features endpoint:
```bash theme={null}
curl \
-X PATCH 'http://localhost:7700/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{"vectorStore": true}'
```
Add documents with vector embeddings using the `_vectors` field:
```bash theme={null}
curl -X POST -H 'content-type: application/json' \
'localhost:7700/indexes/songs/documents' \
--data-binary '[
{"id": 0, "_vectors": [0, 0.8, -0.2], "title": "Across The Universe"},
{"id": 1, "_vectors": [1, -0.2, 0], "title": "All Things Must Pass"},
{"id": 2, "_vectors": [[0.5, 3, 1], [-0.2, 4, 6]], "title": "And Your Bird Can Sing"}
]'
```
Query using vectors with the `/search` or `/multi-search` endpoints:
```bash theme={null}
curl -X POST -H 'content-type: application/json' \
'localhost:7700/indexes/songs/search' \
--data-binary '{"vector": [0, 1, 2]}'
```
Vector search results include a `_semanticScore` field (0 to 1) indicating relevance:
```json theme={null}
{
"hits": [
{
"id": 0,
"_vectors": [
0,
0.8,
-0.2
],
"title": "Across The Universe",
"_semanticScore": 0.6754
}
]
}
```
Note: Vector size must be consistent across all documents in an index.
### Ranking Score Visibility
Use the `showRankingScore` search parameter to see how relevant each document is to your query:
```bash theme={null}
curl \
-X POST 'http://localhost:7700/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{"q": "Batman Returns", "showRankingScore": true}'
```
Each document includes a `_rankingScore` field (0 to 1, higher is more relevant).
### Ranking Score Details (Experimental)
Get detailed scoring breakdowns per ranking rule using the experimental `showRankingScoreDetails` parameter:
```bash theme={null}
curl \
-X PATCH 'http://localhost:7700/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{"scoreDetails": true}'
```
Then use it in searches:
```bash theme={null}
curl \
-X POST 'http://localhost:7700/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{"q": "Batman Returns", "showRankingScoreDetails": true}'
```
Results include a `_rankingScoreDetails` object showing scores for each ranking rule (words, typo, proximity, attribute, exactness).
### Define Searchable Fields at Query Time
The new `attributesToSearchOn` search parameter restricts searches to specific attributes:
```json theme={null}
{
"q": "adventure",
"attributesToSearchOn": [
"genre"
]
}
```
Attributes must be in the searchable attributes list. Given a dataset with documents containing "adventure" in both `name` and `genre` fields, this query returns only documents with "adventure" in the `genre` field.
### Search Facet Values
The new `POST /indexes/{index}/facet-search` endpoint searches within facet values (fields defined as `filterableAttributes`). It supports prefix search and typo tolerance:
```bash theme={null}
curl \
-X POST 'http://localhost:7700/indexes/movies/facet-search' \
-H 'Content-Type: application/json' \
--data-binary '{"facetName": "genres", "facetQuery": "a"}'
```
### Sort Facets by Count
Use the `sortFacetValuesBy` setting to order facet values by frequency. Sort all facets by count:
```bash theme={null}
curl \
-X PATCH 'http://localhost:7700/indexes/movies/settings/faceting' \
-H 'Content-Type: application/json' \
--data-binary '{"sortFacetValuesBy": {"*": "count"}}'
```
Or sort individual facets while keeping others alphabetical:
```bash theme={null}
curl \
-X PATCH 'http://localhost:7700/indexes/movies/settings/faceting' \
-H 'Content-Type: application/json' \
--data-binary '{"sortFacetValuesBy": {"*": "alpha", "genre": "count"}}'
```
### Task Queue Visibility
The `/tasks` route now includes a `total` property showing the total number of tasks in the queue. You can filter this count, for example `/tasks?statuses=succeeded` shows the total number of successfully processed tasks.
## Improvements
### Attribute Ranking Rule Refinement
The `attribute` ranking rule now calculates relevance based on how close a matching word is to that word's position in the query, rather than its absolute distance from the beginning of the attribute. This provides more intuitive ranking when search terms appear at different positions in document fields.
### Language Support Enhancements
* Improved Japanese word segmentation
* Enhanced separator-based tokenization: words containing underscores (`_`) are now properly segmented into separate words, and brackets `{()}` are no longer treated as context separators for the proximity ranking rule
### Performance and Size Improvements
* Reduced index size by approximately 15% through internal database optimization
* Improved deserialization performance
* Re-enabled task autobatching for addition and deletion operations
* Fixed performance issue on `/stats` endpoint
### Metrics Improvements
The experimental Prometheus `/metrics` endpoint now provides:
* Task queue metrics including number of queued and processing tasks
* Real database size used by Meilisearch
* "meilisearch" prefix on all metrics
* `lastUpdate` and `isIndexing` fields in `/stats` endpoint
### Web Interface Update
Updated the local search preview web interface and mini-dashboard to version v0.2.11.
## Other
### Case-Sensitive Search Fix
Fixed case-sensitive search issues with camelCase words. Searches for `dellonghi` now properly match documents containing `DeLonghi`.
### Vector and Geo Fixes
* Fixed geo bounding box queries with string coordinates (requires re-indexing documents with `lat` and `lng` fields)
* Fixed handling of null JSON values in the `_vectors` field
* Fixed panic when using multiple vectors with different dimensions
* Fixed panic when sorting geo fields represented as strings
### Filter Improvements
Fixed filter escaping to properly handle the backslash character at the end of filter values.
### Other Notable Changes
* Fixed highlighting document issues by properly remapping char map when lowercasing strings
* Fixed panic in ranking rule bucket sort algorithm
* Added new `/experimental-features` endpoint for managing experimental features like `scoreDetails` and `vectorStore`
* Fixed document deletion statistics when using filters
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.3.0)
## New Features
### Delete documents by filter
You can now delete documents using filters with the new `/documents/delete` route:
```bash theme={null}
curl -X POST http://localhost:7700/indexes/dogs/documents/delete \
-H 'Content-Type: application/json' \
--data-binary '{ "filter": ["doggo = 'bernese mountain'", "face = cute"] }'
```
Fields must be set as filterable before you can use them as filters.
Meilisearch returns a task object:
```json theme={null}
{
"taskUid": 242,
"indexUid": "dogs",
"status": "enqueued",
"type": "documentDeletion",
"enqueuedAt": "2023-05-03T11:01:58.721841Z"
}
```
Use the returned `taskUid` to check the task status.
### Get documents by filter
You can now use filters in the `GET` endpoint of the `/documents` route:
```bash theme={null}
curl -X GET 'http://localhost:7700/indexes/dogs/documents?limit=1&filter=doggo=bernese'
```
You can also use the new `/documents/fetch` route to handle complex filters:
```bash theme={null}
curl -X POST http://localhost:7700/indexes/dogs/documents/fetch \
-H 'Content-Type: application/json' \
--data-binary '{ "limit": 1, "filter": "doggo = bernese" }'
```
The `/documents/fetch` route accepts: `limit`, `offset`, `fields`, and `filter`.
Fields must be set as filterable before you can use them as filters.
### New filter operators: `IS EMPTY` and `IS NULL`
Two new filter operators have been added:
* `IS EMPTY` matches existing fields with a valid, but empty value
* `IS NULL` matches existing fields with an explicit `null` value
Given the following documents:
```json theme={null}
[
{
"id": 0,
"color":
},
{
"id": 1,
"color": null
},
{
"id": 2
}
]
```
`color IS EMPTY` matches document `0`.
`color IS NULL` matches document `1`.
Both operators work with the `NOT` operator: `color IS NOT EMPTY` and `NOT color IS EMPTY` match document `1`. `color IS NOT NULL` and `NOT color IS NULL` match document `0`.
Neither operator matches documents missing the specified field.
## Improvements
### Search performance and relevancy
The search engine has been significantly refactored to improve performance and relevancy:
**Performance improvements:**
* The fastest 75 percent of queries now consistently answer below 50ms
* Single terms are limited to 150 possible typo matches for queries with 1 typo, and 50 for queries with 2 typos
* Both single word and multi-word queries consider a maximum of 50 synonyms
* The total number of words for all synonyms of a single term cannot exceed 100
* Queries can now contain a maximum of 1000 words
* Geo search performance improvements: faster sorting of small document sets, and descending sort is now as performant as ascending sort
**Relevancy improvements:**
* The `exactness` ranking rule no longer treats synonyms as exact matches, boosting documents containing the query exactly as typed
* Results are always sorted as if the `words` ranking rule has higher priority than `attributes`, `exactness`, `typo`, and `proximity` ranking rules
* Split words are now treated as possible digrams, so `whit ehorse` may match `white horse`
* N-grams and split words are ranked lower than exact words in the `typo` ranking rule
* Ranking rule behavior is now consistent regardless of the number of ranked documents
### Automated task deletion
The task queue now has a maximum limit of 1M tasks. When the limit is reached, Meilisearch automatically deletes the oldest 100k tasks (if they are finished). This prevents database issues when the task queue becomes full.
A hard limit of 10GiB of tasks has been added. When this is reached, Meilisearch will attempt to delete unfinished tasks automatically before rejecting new tasks.
### Language support improvements
* Split camelCase in Latin segmenter
* Improved Arabic normalization and segmentation
### CSV boolean support
CSV documents now support boolean values.
### Other improvements
* Add experimental feature to reduce RAM usage
* Improve geosort error messages
* Improve error message when payload is too large
* Improve the `GET /health` route by ensuring the internal database is accessible
## Other
### Breaking changes in v1.2.1
After upgrading to v1.2.1, you must re-index your dataset. The easiest way is to create a dump and import it into v1.2.1 when starting Meilisearch. This is necessary due to changes in how document deletion statistics are calculated.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.2.0)
## New Features
### Multi-index search
Perform searches across multiple indexes in a single HTTP request using the new `/multi-search` endpoint:
```bash theme={null}
curl \
-X POST 'http://localhost:7700/multi-search' \
-H 'Content-Type: application/json' \
--data-binary '{
"queries": [
{
"indexUid": "products",
"q": "Nike",
"limit": 1
},
{
"indexUid": "brands",
"q": "Nike",
"limit": 1
}
]
}'
```
The endpoint returns an array of results for each queried index:
```json theme={null}
{
"results": [
{
"indexUid": "products",
"hits": ,
"query": "Nike",
"processingTimeMs": 1,
"limit": 1,
"offset": 0,
"estimatedTotalHits": 17
},
{
"indexUid": "brands",
"hits": ,
"query": "Nike",
"processingTimeMs": 0,
"limit": 1,
"offset": 0,
"estimatedTotalHits": 7
}
]
}
```
### facetStats for numerical facets
Queries using the `facets` parameter now automatically include a `facetStats` object containing the minimum and maximum values for each numerical facet:
```bash theme={null}
curl \
-X POST 'http://localhost:7700/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"facets": ["price"]
}'
```
Response:
```json theme={null}
{
"hits": ,
"facetDistribution": {
"price": {}
},
"facetStats": {
"price": {
"min": 2,
"max": 60
}
}
}
```
### Geosearch with bounding box
Use the new `_geoBoundingBox` filter to search for results within a specific geographic area:
```bash theme={null}
curl \
-X POST 'http://localhost:7700/indexes/restaurants/search' \
-H 'Content-type: application/json' \
--data-binary '{
"filter": "_geoBoundingBox([45.472735, 9.184019], [45.473711, 9.185613])"
}'
```
The `_geoBoundingBox` filter accepts two coordinate arrays: the top right corner and the bottom left corner of your search area.
### Prometheus metrics monitoring (experimental)
An experimental feature for monitoring Meilisearch with Prometheus is now available. Launch Meilisearch with the `--experimental-enable-metrics` flag to enable it:
```bash theme={null}
meilisearch --experimental-enable-metrics
```
Metrics will be available at the `/metrics` endpoint in Prometheus-compatible format. This feature is experimental and its API may change between versions.
## Improvements
### Unlimited indexes and index size
Meilisearch no longer enforces limits on the number of indexes or their individual size. You can now create unlimited indexes, with the maximum size determined only by your operating system's memory address space (approximately 80TiB under Linux).
### Customizable CSV delimiters
When adding or updating documents, you can now customize the CSV delimiter using the `csvDelimiter` parameter. The default delimiter remains a comma (`,`).
### Improved language support
* Enhanced Greek support with diacritics normalization and final sigma handling
* Enhanced Arabic support by ignoring Tatweel characters
* Improved language detection during indexing, reducing incorrect language recognition during search
### Better error messages
Meilisearch now provides "did you mean...?" suggestions when you make typos in search parameters, making it easier to identify and correct mistakes.
### Faster indexing with automatic task batching
Addition and deletion tasks are now automatically batched together to improve indexing performance.
### API key and tenant token wildcards
Wildcards (`*`) can now be used at the end of index names when creating API keys or tenant tokens.
### Enhanced geo field handling
The `_geo` field now accepts `null` as a valid value when importing or updating documents.
### Reduced crate size
The Meilisearch crate size has been significantly reduced from approximately 200MB to 50MB through dictionary compression.
### Cached index statistics
Index statistics are now cached to improve performance.
## Other
### Database corruption issue in v1.1.0
v1.1.1 disables the auto-batching feature introduced in v1.1.0 due to a critical bug that could corrupt databases. If your database was affected, the only recovery option is to reindex your documents in a fresh index.
### Task queue overflow protection
Meilisearch now stops receiving new tasks once the task queue reaches capacity, preventing potential issues from task queue overflow.
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.1.0)
## New Features
### Language Support Enhancements
Korean language support has been added to Meilisearch. Chinese language support has been significantly improved with character normalization into Pinyin, optimized segmentation algorithm, and unified character variants handling. Hebrew, Thai, Arabic, and Latin language support has also been enhanced with improved diacritics and non-spacing marks normalization.
### Improved Primary Key Inference
When documents are added to an index without a specified primary key, Meilisearch now intelligently searches for attributes ending with `id` (such as `puid` or `_id`). If exactly one such attribute is found, it becomes the primary key. If multiple candidates are detected, you must explicitly specify the primary key instead of Meilisearch choosing one automatically. This provides better control and prevents unexpected behavior.
### Multi-Version Dump Migration
You can now migrate from any old version of Meilisearch that supports dumps directly to the latest version using a single dump file, making upgrades to v1.0.0 smoother and more straightforward.
## Improvements
### Search and Indexing Performance
Memory usage for search requests containing multiple long words has been significantly improved. The `exactness` ranking rule now performs much better for search requests with many words. Multi-word synonyms are now translated into phrases during query interpretation, which improves result relevancy and stabilizes search latency, particularly for queries with many multi-word synonyms. The `proximity` ranking rule performance has been improved for searches ending with short words. Incremental indexing time for the proximity ranking rule has been reduced. Soft-deletion computation has been improved.
### Configuration and Settings
Settings updates that don't require reindexing no longer trigger unnecessary reindexing, reducing processing time. The `--schedule-snapshot` option now accepts an optional integer value specifying the interval in seconds, consolidating snapshot configuration.
### Error Messages
Error messages have been clarified, particularly when database and engine versions are incompatible, making troubleshooting easier.
### Installation
The `download-latest.sh` script now includes support for Apple Silicon binaries.
## Other
### Security Changes
Master keys in production environments must now be at least 16 bytes long. Keys shorter than this will be rejected as a security measure.
### CLI Configuration Changes
The `--max-index-size` and `--max-task-db` configuration options have been removed. These options were not effectively limiting disk space usage. If these limits impact your usage, please reach out to the Meilisearch team.
The `--disable-auto-batching` CLI option and `MEILI_DISABLE_AUTO_BATCHING` environment variable have been removed. This option was introduced as a temporary workaround and is no longer necessary.
The `--dumps-dir` option has been renamed to `--dump-dir` for consistency.
The `--snapshot-interval-sec` option has been removed. Use `--schedule-snapshot` with an optional integer value instead.
The `--log-level` option and `MEILI_LOG_LEVEL` environment variable now only accept these values: `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE`, and `OFF`.
Hidden CLI arguments `--nb-max-chunks` and `--log-every-n` have been removed.
### Binary Package Name
When installing Meilisearch with `apt`, the command is now `apt install meilisearch` instead of `apt install meilisearch-http`. To install versions before v1.0.0, use `apt install meilisearch-http`.
### Error Code Changes
All task error responses now consistently include an `error` field with a JSON-formatted response containing an error `code` and `type`. Many error codes have been updated for clarity:
**Index Operations** (`POST /indexes`, `PUT /indexes/:uid`, `GET /indexes`):
* `missing_index_uid` replaces `bad_request` when `uid` is missing
* `invalid_index_primary_key` replaces `bad_request` for invalid `primaryKey`
* `invalid_index_limit` and `invalid_index_offset` replace `bad_request` for pagination errors
**Document Operations** (`GET /indexes/:uid/documents`, `POST /indexes/:uid/documents`):
* `invalid_document_fields`, `invalid_document_limit`, and `invalid_document_offset` replace `bad_request`
* `invalid_geo_field` replaces `invalid_document_geo_field`
* Attempting to update the primary key when adding documents now returns an error
**Search Parameters** (`GET /indexes/:uid/search`, `POST /indexes/:uid/search`):
* `invalid_search_q`, `invalid_search_offset`, `invalid_search_limit`, `invalid_search_page`, `invalid_search_hits_per_page` replace `bad_request`
* `invalid_search_attributes_to_retrieve`, `invalid_search_attributes_to_crop`, `invalid_search_show_matches_position` replace `bad_request`
* `invalid_search_filter` replaces `invalid_filter`
* `invalid_search_sort` replaces `invalid_sort`
* `invalid_search_facets`, `invalid_search_highlight_pre_tag`, `invalid_search_highlight_post_tag`, `invalid_search_matching_strategy` replace `bad_request`
**Index Swap** (`POST /indexes/swap-indexes`):
* `invalid_swap_duplicate_index_found` replaces `duplicate_index_found`
* `invalid_swap_indexes` replaces `bad_request` when swap array doesn't contain exactly 2 indexes
* `missing_swap_indexes` replaces `missing_parameter` when `indexes` field is missing
**Settings Routes** (all `/settings` and sub-routes):
* `invalid_settings_displayed_attributes`, `invalid_settings_searchable_attributes`, `invalid_settings_filterable_attributes`, `invalid_settings_sortable_attributes` replace `bad_request`
* `invalid_settings_ranking_rules` replaces `bad_request`
* `invalid_settings_stop_words`, `invalid_settings_synonyms` replace `bad_request`
* `invalid_settings_distinct_attribute` replaces `bad_request`
* `invalid_settings_typo_tolerance` replaces `invalid_typo_tolerance_min_word_size_for_typos` and `bad_request`
* `invalid_settings_faceting`, `invalid_settings_pagination` replace `bad_request`
**Task Filters** (`GET /tasks`):
* `invalid_task_uids` replaces `invalid_task_uids_filter`
* `invalid_task_types` replaces `invalid_task_types_filter`
* `invalid_task_statuses` replaces `invalid_task_statuses_filter`
* `invalid_task_cancel_by` replaces `invalid_task_canceled_by_filter`
* `invalid_task_before_enqueued_at`, `invalid_task_after_enqueued_at`, `invalid_task_before_started_at`, `invalid_task_after_started_at`, `invalid_task_before_finished_at`, `invalid_task_after_finished_at` replace `invalid_task_date_filter`
**API Key Operations** (`GET /keys`, `POST /keys`, `PATCH /keys`):
* `invalid_api_key_limit`, `invalid_api_key_offset` replace `bad_request`
* `missing_api_key_actions`, `missing_api_key_indexes`, `missing_api_key_expire_at` replace `missing_parameter`
* `immutable_api_key_uid`, `immutable_api_key_actions`, `immutable_api_key_indexes`, `immutable_api_key_expires_at`, `immutable_api_key_created_at`, `immutable_api_key_updated_at` replace `immutable_field`
**System Errors**:
* `no_space_left_on_device` replaces `internal` when disk space is exhausted
* `io_error` replaces `internal` for I/O errors
* `too_many_open_files` replaces `internal` when the open files limit is exceeded
* All errors of type `system` now return HTTP status code `500`
**Synchronous Error Validation**:
The following errors are now returned synchronously instead of as failed tasks:
* `invalid_index_uid`
* `invalid_settings_ranking_rules`
* `invalid_settings_typo_tolerance` when `oneTypo` and `twoTypos` are filled but invalid for `minWordSizeForTypos`
[Find more information on GitHub](https://github.com/meilisearch/meilisearch/releases/tag/v1.0.0)
# E-commerce
Source: https://www.meilisearch.com/docs/resources/demos/ecommerce
Product search with facets, filtering, sorting, image search, and infinite scroll
**Live demo**: [ecommerce.meilisearch.com](https://ecommerce.meilisearch.com)
A complete e-commerce search experience showcasing Meilisearch's advanced filtering, sorting, and image search capabilities.
## Key features
* **Rich faceting**: Multiple facet categories (gender, category, subcategory, color) with counts, letting users drill down into product catalogs.
* **Search within facets**: Type in any facet panel to quickly find specific filter values across large facet lists.
* **Sorting**: Switch between featured, price, and other sort orders from the dropdown.
* **Filtering**: Combine multiple filters across categories for precise product discovery.
* **Image search**: Click the image icon in the search bar to search products by uploading a picture.
* **Infinite scroll**: Browse results seamlessly without pagination, loading more products as you scroll.
## Links
Experience e-commerce search
View implementation on GitHub
# Federated Search
Source: https://www.meilisearch.com/docs/resources/demos/federated_search
Compare multi-search with and without result federation
**Live demo**: [federated-search.meilisearch.com](https://federated-search.meilisearch.com)
Explore Meilisearch's federated search in action. Search across a movies index and an actors index, then toggle between two modes: independent per-index results displayed side by side, or a single ranked list
merged across both indexes.
## Links
Toggle between multi-index and federated modes
Use federated search in your app
# Image Search
Source: https://www.meilisearch.com/docs/resources/demos/flickr
Multimodal search across 100 million images with similar image discovery
**Live demo**: [flickr.meilisearch.com](https://flickr.meilisearch.com)
Search 100 million Flickr images using multimodal search. Type natural language descriptions like "autumn colors" and find matching images instantly. This is not vector search on text metadata, it is true multimodal search where the model understands visual content.
## Key features
* **Multimodal search**: Search images by describing what you want in natural language. The model processes both text queries and image content in the same embedding space.
* **Similar images**: Click any result to discover visually similar images using the similar documents API.
* **Massive scale**: 100 million images indexed and searchable with sub-second response times.
* **Built with Nova Embeddings**: Powered by Amazon Nova Embeddings from AWS for high-quality multimodal understanding.
## Links
Search 100 million images
Implement image search in your app
# Geosearch
Source: https://www.meilisearch.com/docs/resources/demos/geosearch
Search cities on an interactive map with geo filtering and distance sorting
**Live demo**: [geosearch-js.meilisearch.com](https://geosearch-js.meilisearch.com)
Search 30,000+ cities and see results displayed on an interactive Google Map. Combines text search with geographic filtering and distance-based sorting.
## Links
Search cities on a map
View implementation
# Home Booking
Source: https://www.meilisearch.com/docs/resources/demos/home_booking
Airbnb-style home booking demo with conversational chat search and classic search powered by Meilisearch
**Live demo**: [lodging-lark.lovable.app](https://lodging-lark.lovable.app/)
Home Booking is an Airbnb-style property search application that combines classic search with a conversational chat interface. Describe your ideal stay in natural language and let the AI refine results as you add constraints, or switch to classic search for direct filtering.
## Key features
* **Chat search**: Describe your ideal stay in plain language (e.g., "Find me a family-friendly beach house in Europe under \$300/night") and get relevant property results with matching keywords highlighted.
* **Iterative refinement**: Narrow results through follow-up messages. Start broad, then refine: "Only alpine cabins" then "for 8 persons" to progressively filter down to the perfect match.
* **Classic search**: Switch between chat and traditional keyword search with filters for a familiar booking experience.
* **Meilisearch chat route**: Uses the `/chat` route with ChatGPT 5.2 as the LLM and [Zembed](https://www.zeroentropy.dev/) embeddings via HuggingFace Inference Endpoints for semantic understanding.
* **Built with Lovable**: The frontend is entirely generated with Lovable, demonstrating how quickly you can prototype a full search experience on top of Meilisearch.
This demo has minimal guardrails. The chat interface may occasionally produce unexpected responses.
## Example conversation
1. **"Find me a family-friendly beach house in Europe under \$300/night"** - Returns 6 beach properties across Italy, Spain, and Greece, all under budget, with matching terms highlighted.
2. **"I would like only alpine cabins"** - Filters down to 4 cabin-style properties in alpine regions.
3. **"For 8 persons"** - Narrows to a single property that fits all criteria.
## Links
Search properties with chat or classic search
Learn about Meilisearch conversational search
# Hydration
Source: https://www.meilisearch.com/docs/resources/demos/hydration
Search results enriched with related data from other indexes using foreign keys
**Live demo**: [demo-hydration.vercel.app/](https://demo-hydration.vercel.app/)
Demonstrates how Meilisearch's foreign keys feature automatically replaces foreign IDs in search results with full documents from a referenced index.
## Links
See hydration in action
Configure foreign keys in your index
# MoMA Collection
Source: https://www.meilisearch.com/docs/resources/demos/moma
Search the Museum of Modern Art's collection by artist, title, or medium
**Live demo**: [moma.meilisearch.com](https://moma.meilisearch.com)
Search through the Museum of Modern Art's collection of artworks. Filter by artist, nationality, medium, and department. Demonstrates faceted search over cultural and historical data with multi-language name handling.
## Links
Explore the MoMA collection
# Music Search
Source: https://www.meilisearch.com/docs/resources/demos/music
Search 40 million songs with instant results
**Live demo**: [music.meilisearch.com](https://music.meilisearch.com)
Search across 40 million songs from the MusicBrainz database. This demo highlights Meilisearch's performance at scale with sub-100ms response times and typo-tolerant search across artists, albums, and tracks.
## Links
Search 40 million songs
# Nobel Prizes
Source: https://www.meilisearch.com/docs/resources/demos/nobel_prizes
Search Nobel Prize winners by name, category, or achievement
**Live demo**: [nobel-prizes.meilisearch.com](https://nobel-prizes.meilisearch.com)
Search the complete history of Nobel Prize winners from 1901 to present. Filter by category (Physics, Chemistry, Medicine, Literature, Peace, Economics) and year range. Demonstrates faceted search over biographical and historical data.
## Links
Search Nobel laureates
# Demos
Source: https://www.meilisearch.com/docs/resources/demos/overview
Explore interactive demos showcasing Meilisearch capabilities
Discover what Meilisearch can do through our collection of interactive demos. Each demo showcases different features and use cases, with full source code available on GitHub.
## Featured demos
Hybrid search, recommendations, multi-lingual semantic search, and custom ranking.
Chat-based property search with conversational refinement and classic search.
Facets, filtering, sorting, image search, and infinite scroll.
Multi-tenancy with native data isolation per user.
Multimodal search across 100 million images with similar image discovery.
Real-time search personalization with editable user context.
Compare 20+ embedders and search modes side by side.
## More demos
40 million songs with sub-100ms response times.
Search 30,000+ cities on an interactive map.
Explore the Museum of Modern Art's collection.
Search Nobel Prize winners with category filtering.
Multi-tenant data isolation with JWT tokens.
Compare different typo tolerance configurations.
Search using voice input with speech recognition.
Search Ruby packages with popularity-based ranking.
## InstantSearch examples
Interactive CodeSandbox examples showing Meilisearch integration with InstantSearch libraries.
Vanilla JavaScript with instant-meilisearch
Vue InstantSearch integration
React InstantSearch integration
## Build your own
Deploy in minutes with Meilisearch Cloud
Run Meilisearch on your own infrastructure
Demo source code is available on GitHub, with most demos in the [meilisearch/demos](https://github.com/meilisearch/demos) repository.
# Personalized Search
Source: https://www.meilisearch.com/docs/resources/demos/personalized_search
Dynamic search personalization with real-time user context customization
**Live demo**: [p13n-demo-reranking.vercel.app](https://p13n-demo-reranking.vercel.app)
This demo showcases Meilisearch's personalization feature. Search for movies while customizing the user context on the fly. Toggle personalization on or off, edit the user prompt describing preferences, and watch results rerank in real time based on the user profile.
## Key features
* **Real-time personalization**: Toggle the "Personalize" switch to see how results change based on user context.
* **Editable user profile**: Modify the user preference prompt (e.g., "The user prefers genres: Action, Adventure, Sci-Fi") and see results rerank instantly.
* **Genre presets**: Quick-select genre combinations like "Thriller & Crime" or "Science Fiction & Action" to see how different preferences affect ranking.
* **Transparent reranking**: Compare personalized vs. non-personalized results to understand the impact of context on relevancy.
## Links
Explore personalized search
Implement personalization
# Search Playground
Source: https://www.meilisearch.com/docs/resources/demos/playground
Compare search engines and embedders side by side
**Live demo**: [playground.meilisearch.com](https://playground.meilisearch.com)
The Search Playground lets you compare semantic and hybrid search side by side. Test over 20 different embedders, measure performance, and evaluate relevancy differences across configurations.
## Key features
* **Side-by-side comparison**: Run the same query with two different configurations and compare results in real time.
* **20+ embedders**: Switch between a wide range of embedding models to compare quality and performance.
* **Performance metrics**: See response times for each configuration to understand the speed/relevancy tradeoff.
* **Search mode switching**: Compare full-text search, semantic search, and hybrid search on the same dataset.
## Links
Compare search configurations
Learn about embedder options
# RubyGems Finder
Source: https://www.meilisearch.com/docs/resources/demos/rubygems
Search Ruby packages with popularity-based ranking
**Live demo**: [rubygems.meilisearch.com](https://rubygems.meilisearch.com)
Search the complete RubyGems package registry. Popular packages are ranked higher using custom ranking rules on download counts. Demonstrates how Meilisearch can power developer tool and package registry search.
## Links
Search Ruby packages
# SaaS CRM Search
Source: https://www.meilisearch.com/docs/resources/demos/saas
Multi-tenant CRM search with native data isolation per user
**Live demo**: [saas.meilisearch.com](https://saas.meilisearch.com)
This SaaS demo showcases Meilisearch's multi-tenancy capabilities in a CRM context. The core feature is native data isolation: switch between users and each one sees a completely different view of the data, even though all documents are stored in the same index.
## Key features
* **Multi-tenancy**: Switch users to see entirely different results. Each user's data is isolated natively at the search engine level, with no application-side filtering required.
* **Federated search**: Query contacts, companies, and deals in a single request, with results grouped by type.
* **Universal search bar**: One search input covers the entire application.
## Links
Experience multi-tenant search
View implementation on GitHub
# Tenant Tokens
Source: https://www.meilisearch.com/docs/resources/demos/tenant_tokens
Multi-tenant data isolation with server-side JWT filtering
**Live demo**: [tenant-token.meilisearch.com](https://tenant-token.meilisearch.com)
Demonstrates Meilisearch's tenant token feature for multi-tenant applications. Each user only sees documents they are authorized to access, with filters enforced server-side via JWT tokens that cannot be bypassed client-side.
## Links
See tenant isolation in action
Implement in your app
# Typo Tolerance
Source: https://www.meilisearch.com/docs/resources/demos/typo_tolerance
Experiment with different typo tolerance configurations
**Live demo**: [typo-tolerance.meilisearch.com](https://typo-tolerance.meilisearch.com)
Explore Meilisearch's typo tolerance in action. Search and compare results side by side between a default and a custom configuration.
## Links
Experiment with typo settings
Configure for your app
# Voice Search
Source: https://www.meilisearch.com/docs/resources/demos/voice_search
Search using voice input with browser speech recognition
**Live demo**: [voice.meilisearch.com](https://voice.meilisearch.com)
Voice-enabled search integrating Meilisearch with the browser's Web Speech API. Speak your query and see results appear in real time as words are recognized.
## Links
Search with your voice
# Where to Watch
Source: https://www.meilisearch.com/docs/resources/demos/where_to_watch
Movie discovery app showcasing hybrid search, recommendations, and multi-lingual semantic search
**Live demo**: [where2watch.meilisearch.com](https://where2watch.meilisearch.com)
Where to Watch is a movie discovery application powered by Meilisearch's hybrid search. Use the search slider to blend full-text and semantic search, explore movie recommendations, and see how custom ranking promotes the most relevant results.
[
](https://where2watch.meilisearch.com)
## Key features
* **Hybrid search slider**: Move from 0 (full-text search) to 100 (semantic search) to find the perfect balance. The middle ground combines both approaches for optimal relevancy.
* **Recommendation API**: Select a movie to see similar titles powered by the similar documents endpoint. Recommendations surface movies in the same category or with similar themes.
* **Multi-lingual semantic search**: Semantic search works across languages. Searching "Le seigneur des anneaux" finds "The Lord of the Rings" without manual translation.
* **Automatic synonym handling**: Queries like "last tldr movie" find "The Lord of the Rings" movies, with the semantic engine understanding intent beyond exact keywords.
* **Custom ranking rules**: Recent movies and highly rated titles are promoted in results, blending relevancy with freshness and popularity.
* **Performance**: Sub-50ms response times, even with hybrid search combining full-text and vector retrieval.
## Links
Search movies and explore hybrid search
View implementation on GitHub
# Carbon footprint of Meilisearch Cloud regions
Source: https://www.meilisearch.com/docs/resources/help/carbon_footprint
Understand the grid carbon intensity displayed next to Meilisearch Cloud regions, how it is calculated, and why it matters.
When selecting a region for your Meilisearch Cloud project, you may notice a green leaf icon next to certain regions. This page explains what that means and how we calculate the associated figures.
## What is grid carbon intensity?
Grid carbon intensity measures how much CO2 is emitted, on average, to produce one kilowatt-hour (kWh) of electricity in a given location. It is expressed in **grams of CO2 equivalent per kilowatt-hour (gCO2e/kWh)**.
A lower number means the local electricity grid relies more on low-carbon energy sources (hydro, wind, solar, nuclear), while a higher number indicates a heavier reliance on fossil fuels (coal, gas).
The green leaf icon highlights regions with a **low carbon intensity (below 150 gCO2e/kWh)**.
## How is it calculated?
We use the **location-based methodology** recommended by the [GHG Protocol](https://ghgprotocol.org/). This approach reflects the actual carbon mix of the local electricity grid, regardless of any renewable energy certificates (RECs) or power purchase agreements (PPAs) the cloud provider may have purchased.
We chose this methodology because it gives the most honest, comparable picture across regions.
Values are annual averages. Real-time intensity varies by hour, season, and weather conditions. We review these figures yearly as grid mixes evolve.
## Data sources
Figures are aggregated from the following sources:
* **US regions**: [EPA eGRID](https://www.epa.gov/egrid/download-data)
* **European regions**: [European Environment Agency](https://www.eea.europa.eu/data-and-maps/daviz/co2-emission-intensity-9/)
* **Asia-Pacific and South America**: [IEA Emissions Factors 2025](https://www.iea.org/data-and-statistics/data-product/emissions-factors-2025)
* **Aggregated coefficients**: [Cloud Carbon Footprint open methodology](https://www.cloudcarbonfootprint.org/docs/methodology/)
## Why we share this
Carbon intensity is becoming an important criterion for many teams, particularly in enterprise procurement. We want to give you the information you need to make an informed choice. Showing this data does not restrict access to any region; it is provided for transparency only.
If you have questions or feedback, feel free to reach out via [Discord](https://discord.gg/meilisearch) or the [Meilisearch helpdesk](https://help.meilisearch.com/).
# Contributing to our documentation
Source: https://www.meilisearch.com/docs/resources/help/contributing_docs
The Meilisearch documentation is open-source. Learn how to help make it even better.
This documentation website is hosted in a [public GitHub repository](https://github.com/meilisearch/documentation). It is built with [Next.js](https://nextjs.org), written in [MDX](https://mdxjs.com), and deployed on [Vercel](https://www.vercel.com).
## Our documentation philosophy
Our documentation aims to be:
* **Efficient**: we don't want to waste anyone's time
* **Accessible**: reading the texts here shouldn't require native English or a computer science degree
* **Thorough**: the documentation website should contain all information anyone needs to use Meilisearch
* **Open source**: this is a resource by Meilisearch users, for Meilisearch users
## How to contribute?
Both options below require a [GitHub account](https://github.com/signup). Create one if you don't have it yet.
The two most common ways to contribute are:
1. **Opening an [issue](https://github.com/meilisearch/documentation/issues/new)**: to report a problem, request an improvement, or suggest new content
2. **Updating the documentation content by opening a Pull Request (PR)**: either by creating the PR from your local text editor, or directly from GitHub. Before opening a PR, check our [open issues](https://github.com/meilisearch/documentation/issues) to see if one already exists for your change. In most cases, it's a good idea to [open an issue](https://github.com/meilisearch/documentation/issues/new) first so you can coordinate with the maintainers.
### Creating a PR from your local editor
To edit the docs in your preferred editor and open a PR, follow the detailed instructions in our [CONTRIBUTING.md](https://github.com/meilisearch/documentation/blob/main/CONTRIBUTING.md).
### Editing content directly on GitHub
The simplest way to update the docs is to use the "Edit this page" link at the bottom left of every page. Follow these steps:
1. Go to the documentation page you'd like to edit, scroll down, and click **"Edit this page"** at the bottom left of the screen. This will take you to GitHub
2. You may be prompted to [fork the repository](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo)
3. Use GitHub's text editor to update the page
4. Scroll down until you reach the box named **"Propose changes"**
5. Fill in the first field with a short, descriptive title (e.g. "Fix typo in search API reference")
6. Use the second field to add a brief explanation of your changes
7. Click **"Propose changes"**. You should see a "Comparing changes" page
8. Check that the base repository is `meilisearch/documentation` and the base branch is `main`
9. Click **"Create pull request"**
10. A documentation maintainer will review your PR shortly. If everything looks good, your changes will be merged and published. You're now a Meilisearch contributor! 🚀
## How we review contributions
### How we review issues
When **reviewing issues**, we consider a few criteria:
1. Is this task a priority for the documentation maintainers?
2. Is the documentation website the best place for this information? Sometimes an idea might work better on our blog than the docs, or it might be more effective to link to an external resource than write and maintain it ourselves
3. If it's a bug report, can we reproduce the error?
If users show interest in an issue by upvoting or reporting similar problems, it is more likely the documentation will dedicate resources to that task.
### How we review PRs
For **reviewing contributor PRs**, we start by making sure the PR is up to our **quality standard**.
We ask the following questions:
1. Is the information **accurate**?
2. Is it **easy to understand**?
3. Do the code samples run without errors? Do they help users understand what we are explaining?
4. Is the English **clear and concise**? Can a non-native speaker understand it?
5. Is the grammar perfect? Are there any typos?
6. Can we shorten text **without losing any important information**?
7. Do the suggested changes require updating other pages in the documentation website?
8. In the case of new content, is the article in the right place? Should other articles in the documentation link to it?
Nothing makes us happier than a thoughtful and helpful PR. Your PRs often save us time and effort, and they make the documentation **even stronger**.
Our only major requirement for PR contributions is that the author responds to communication requests within a reasonable time frame.
Once you've opened a PR in this repository, one of our team members will stop by shortly to review it. If your PR is approved, nothing further is required from you. However, **if in seven days you have not responded to a request for further changes or more information, we will consider the PR abandoned and close it**.
If this happens to you and you think there has been some mistake, please let us know and we will try to rectify the situation.
## Contributing to Meilisearch
There are many ways to contribute to Meilisearch directly as well, such as:
* Contributing to the [main engine](https://github.com/meilisearch/meilisearch/blob/main/CONTRIBUTING.md)
* Contributing to [our integrations](https://github.com/meilisearch/integration-guides)
* [Creating an integration](https://github.com/meilisearch/integration-guides/blob/main/resources/build-integration.md)
* Share your feedback and usecases on our [GitHub Discussions](https://github.com/orgs/meilisearch/discussions)
* Creating written or video content (tutorials, blog posts, etc.)
There are also many valuable ways of supporting the above repositories:
* Giving feedback
* Suggesting features
* Creating tests
* Fixing bugs
* Adding content
* Developing features
# Experimental features overview
Source: https://www.meilisearch.com/docs/resources/help/experimental_features_overview
This article covers how to activate and configure Meilisearch experimental features.
Meilisearch periodically introduces new experimental features. Experimental features are not always ready for production, but offer functionality that might benefit some users.
An experimental feature's API can change significantly and become incompatible between releases. Keep this in mind when using experimental features in a production environment.
Meilisearch makes experimental features available expecting they will become stable in a future release, but this is not guaranteed.
## Activating experimental features
Experimental features fall into two groups based on how they are activated or deactivated:
1. Those that are activated at launch with a command-line flag or environment variable
2. Those that are activated with the [`/experimental-features` API route](/docs/reference/api/management/list-experimental-features).
## Activating experimental features at launch
Some experimental features can be [activated at launch](/docs/resources/self_hosting/configuration/overview), for example with a command-line flag:
```sh theme={null}
./meilisearch --experimental-enable-metrics
```
Flags and environment variables for experimental features are not included in the [regular configuration options list](/docs/resources/self_hosting/configuration/reference#all-instance-options). Instead, consult the specific documentation page for the feature you are interested in, which can be found in the experimental section.
Command-line flags for experimental features are always prefixed with `--experimental`. Environment variables for experimental features are always prefixed with `MEILI_EXPERIMENTAL`.
Activating or deactivating experimental features this way requires you to relaunch Meilisearch.
### Activating experimental features during runtime
Some experimental features can be activated via an HTTP call using the [`/experimental-features` API route](/docs/reference/api/management/list-experimental-features):
```bash cURL theme={null}
curl \
-X PATCH 'MEILISEARCH_URL/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{
"metrics": true
}'
```
```python Python theme={null}
client.update_experimental_features({"metrics": True})
```
```ruby Ruby theme={null}
client.update_experimental_features(metrics: true)
```
```go Go theme={null}
client.ExperimentalFeatures().SetMetrics(true).Update()
```
```rust Rust theme={null}
let client = Client::new("MEILISEARCH_URL", Some("apiKey"));
let features = ExperimentalFeatures::new(&client);
features.set_metrics(true)
let res = features
.update()
.await
.unwrap();
```
Activating or deactivating experimental features this way does not require you to relaunch Meilisearch.
The **logs** and **metrics** experimental features are not available on Meilisearch Cloud. Both require controlling the Meilisearch process at launch to enable the `--experimental-enable-logs-route` and `--experimental-enable-metrics` flags, which Cloud users cannot do. Use Cloud's built-in Analytics dashboard for observability instead.
## Current experimental features
| Name | Description | How to configure |
| ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
| [Limit task batch size](/docs/resources/self_hosting/configuration/overview) | Limits number of tasks processed in a single batch | CLI flag or environment variable |
| [Log customization](/docs/reference/api/logs) | Customize log output and set up log streams | CLI flag or environment variable, API route |
| [Metrics API](/docs/reference/api/metrics) | Exposes Prometheus-compatible analytics data | CLI flag or environment variable, API route |
| [Reduce indexing memory usage](/docs/resources/self_hosting/configuration/overview) | Optimizes indexing performance | CLI flag or environment variable |
| [Search queue size](/docs/resources/self_hosting/configuration/overview) | Configure maximum number of concurrent search requests | CLI flag or environment variable |
| [Drop search after](/docs/resources/self_hosting/configuration/overview) | Drop irrelevant search requests after a configurable timeout (default: 60s) | CLI flag or environment variable |
| [Searches per core](/docs/resources/self_hosting/configuration/overview) | Configure number of concurrent search requests per CPU core (default: 4) | CLI flag or environment variable |
| [`CONTAINS` filter operator](/docs/capabilities/filtering_sorting_faceting/advanced/filter_expression_syntax#contains) | Enables usage of `CONTAINS` with the `filter` search parameter | CLI flag or environment variable, API route |
| [Edit documents with function](/docs/capabilities/indexing/how_to/edit_documents_with_functions) | Use a [Rhai](https://rhai.rs/book/) function to edit documents directly in the Meilisearch database | API route |
| [`/network` route](/docs/reference/api/network/get-network) | Enable `/network` route | API route |
| [Composite embedders](/docs/reference/api/settings/get-embedders) | Enable composite embedders | API route |
| [Chat completions](/docs/reference/api/chats/update-settings-of-a-chat-workspace) | Enable chat completion capabilities | API route |
| [Get task documents route](/docs/reference/api/async-task-management/list-tasks) | Enable route to retrieve documents from tasks | API route |
| [Search query embedding cache](/docs/resources/self_hosting/configuration/reference#search-query-embedding-cache) | Enable a cache for search query embeddings | CLI flag or environment variable |
| [Maximum batch payload size](/docs/resources/self_hosting/configuration/reference#maximum-batch-payload-size) | Limit batch payload size | CLI flag or environment variable |
| [Multimodal search](/docs/reference/api/settings/list-all-settings) | Enable multimodal search | API route |
| [Disable new indexer](/docs/resources/self_hosting/configuration/overview) | Use previous settings indexer | CLI flag or environment variable |
| [Allowed IP networks](/docs/resources/self_hosting/configuration/overview) | Override default IP policy with allowed CIDR ranges | CLI flag or environment variable |
| [Search personalization](/docs/capabilities/personalization/getting_started/personalized_search) | Enables search personalization | CLI flag or environment variable |
| [Search rules](/docs/capabilities/search_rules/overview) | Curate search results by pinning selected documents when query- or time-based conditions match | API route |
| [Foreign keys](/docs/capabilities/indexing/joins/define_index_relationships) | Link documents across indexes to enrich search results with related data and filter by related document properties | API route |
| [Task queue compaction](/docs/reference/api/async-task-management/compact-task-queue) | Compact the task queue database to reclaim space for new tasks | API route |
| [Disable documents fetch queue ](/docs/reference/api/async-task-management/get-tasks-document-payload) | Disable the documents fetch queue, which forces document fetch routes to wait in the search queue when no thread is available | API route |
| [Render template](/docs/reference/api/template/render-template) | Render document templates and fragments against any input to test embedder configuration | API route |
# FAQ
Source: https://www.meilisearch.com/docs/resources/help/faq
Common questions about Meilisearch features, setup, performance, and troubleshooting.
## I have never used a search engine before. Can I use Meilisearch anyway?
Of course! No knowledge of Elasticsearch or Solr is required to use Meilisearch. It is designed to be **easy to use** and accessible to all developers.
The fastest way to get started is with [Meilisearch Cloud](/docs/getting_started/first_project), which gives you a running instance in minutes. You can also [self-host Meilisearch](/docs/resources/self_hosting/getting_started/quick_start) if you prefer.
We provide [SDKs](/docs/resources/help/sdks) for many languages and frameworks to help you integrate Meilisearch into your project.
## Should I use Meilisearch Cloud or self-host?
[Meilisearch Cloud](https://www.meilisearch.com/cloud) is the recommended option for most users. It handles provisioning, scaling, backups, and updates automatically, and includes built-in analytics and monitoring.
Self-hosting gives you full control over your infrastructure and is available under the MIT license (Community Edition). It requires managing your own servers, updates, and backups.
See the [Cloud quick start](/docs/getting_started/first_project) or [self-hosting guide](/docs/resources/self_hosting/getting_started/quick_start) to get started with either option.
## How does Meilisearch compare to other search engines?
We maintain detailed [comparisons](/docs/resources/comparisons/alternatives) with Elasticsearch, Algolia, Typesense, and others. You can also try Meilisearch with your own data: the [Cloud free trial](https://www.meilisearch.com/cloud) requires no credit card.
## What are Meilisearch's limits?
Key limits include:
| Limit | Value |
| ---------------------------------- | ------------------------------- |
| Max documents per index | \~4.3 billion |
| Max index size | \~80 TiB (recommended \< 2 TiB) |
| Max attributes per document | 65,536 |
| Max query terms (full-text search) | 10 words (configurable) |
| Default max results per search | 1,000 (configurable) |
For the full list, see [known limitations](/docs/resources/help/known_limitations).
## How do I update Meilisearch?
Meilisearch Cloud instances can be updated with one click from the Cloud dashboard. For self-hosted instances, see the [update guide](/docs/resources/migration/updating), which covers upgrading with the `--upgrade-db` flag and dump-based migration for older versions.
## I keep getting a `400 - Bad Request` when adding documents
This usually means your data is not in a valid format. Common causes include extraneous commas, mismatched brackets, or missing quotes. Meilisearch accepts JSON, CSV, and NDJSON formats.
When [adding or replacing documents](/docs/reference/api/documents/add-or-replace-documents), you must enclose them in an array even if there is only one document.
## I uploaded documents but get no search results
Your document upload likely failed. Check the status of the task using the returned [`taskUid`](/docs/reference/api/async-task-management/get-task). If the task failed, the response contains an `error` object:
```json theme={null}
{
"uid": 1,
"indexUid": "movies",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 67493,
"indexedDocuments": 0
},
"error": {
"message": "Document does not have a `:primaryKey` attribute: `:documentRepresentation`.",
"code": "missing_document_id",
"type": "invalid_request",
"link": "https://www.meilisearch.com/docs/reference/errors/error_codes#missing_document_id"
},
"duration": "PT1S",
"enqueuedAt": "2021-08-10T14:29:17.000000Z",
"startedAt": "2021-08-10T14:29:18.000000Z",
"finishedAt": "2021-08-10T14:29:19.000000Z"
}
```
## Is killing a Meilisearch process safe?
Yes. Killing Meilisearch is **safe**, even during indexing. When you restart, it resumes the task from the beginning. See the [asynchronous operations guide](/docs/capabilities/indexing/tasks_and_batches/async_operations) for more details.
## Can I use Meilisearch for multi-tenant applications?
Yes. Meilisearch supports [multitenancy with tenant tokens](/docs/capabilities/security/overview), which let you control which documents each user can search without maintaining separate indexes.
## What are the hardware requirements for self-hosting?
This depends on your dataset size, number of searchable/filterable fields, and query volume. As a starting point, provision a machine with at least **ten times the disk space** of your raw dataset.
Key considerations:
* **RAM**: Search speed depends on the ratio between RAM and database size. More RAM means faster searches.
* **Disk**: More searchable/filterable fields and ranking rules increase database size.
* **CPU cores**: More cores let Meilisearch handle more concurrent search queries.
For optimization tips, see [RAM and multi-threading performance](/docs/resources/self_hosting/performance/ram_multithreading).
**Always update index settings before adding documents.** This avoids double-indexing and reduces memory spikes.
## Is there a public roadmap?
Yes. Visit the [public roadmap](https://roadmap.meilisearch.com/) to see planned features and ongoing work.
## Does Meilisearch collect telemetry?
Meilisearch collects **anonymous usage data** to understand feature usage and detect bugs. It never tracks or identifies individual users.
You can read what is collected and how to opt out on the [telemetry page](/docs/resources/help/telemetry). For privacy concerns, email [privacy@meilisearch.com](mailto:privacy@meilisearch.com).
# Known limitations
Source: https://www.meilisearch.com/docs/resources/help/known_limitations
Meilisearch has a number of known limitations. These are hard limits you cannot change and should take into account when designing your application.
Meilisearch has a number of known limitations. Some of these limitations are the result of intentional design trade-offs, while others can be attributed to [LMDB](/docs/resources/internals/storage), the key-value store that Meilisearch uses under the hood.
This article covers hard limits that cannot be altered. Meilisearch also has some default limits that *can* be changed, such as a [default payload limit of 100MB](/docs/resources/self_hosting/configuration/reference#payload-limit-size) and a [default search limit of 20 hits](/docs/reference/api/search/search-with-post#body-limit).
## Maximum Meilisearch Cloud upload size
**Limitation:** The maximum file upload size when using the Meilisearch Cloud interface is 20mb.
**Explanation:** Handling large files may result in degraded user experience and performance issues. To add datasets larger than 20mb to a Meilisearch Cloud project, use the [add documents endpoint](/docs/reference/api/documents/add-or-replace-documents) or [`meilisearch-importer`](https://github.com/meilisearch/meilisearch-importer).
## Maximum number of query words
**Limitation:** The maximum number of terms taken into account for each [search query](/docs/reference/api/search/search-with-post#body-q) is 10. If a search query includes more than 10 words, all words after the 10th will be ignored.
**Explanation:** Queries with many search terms can lead to long response times. This goes against our goal of providing a fast search-as-you-type experience.
## Maximum number of words per attribute
**Limitation:** Meilisearch can index a maximum of 65535 positions per attribute. Any words exceeding the 65535 position limit will be silently ignored.
**Explanation:** This limit is enforced for relevancy reasons. The more words there are in a given attribute, the less relevant the search queries will be.
### Example
Suppose you have three similar queries: `Hello World`, `Hello, World`, and `Hello - World`. Due to how our tokenizer works, each one of them will be processed differently and take up a different number of "positions" in our internal database.
If your query is `Hello World`:
* `Hello` takes the position `0` of the attribute
* `World` takes the position `1` of the attribute
If your query is `Hello, World`:
* `Hello` takes the position `0` of the attribute
* `,` takes the position `8` of the attribute
* `World` takes the position `9` of the attribute
`,` takes 8 positions as it is a hard separator. You can read more about word separators in our [article about data types](/docs/resources/internals/datatypes#string).
If your query is `Hello - World`:
* `Hello` takes the position `0` of the attribute
* `-` takes the position `1` of the attribute
* `World` takes the position `2` of the attribute
`-` takes 1 position as it is a soft separator. You can read more about word separators in our [article about data types](/docs/resources/internals/datatypes#string).
## Maximum number of attributes per index
**Limitation:** Meilisearch can index a maximum of **65,536 attributes per index**. If an index contains more than 65,536 attributes, an error will be thrown.
**Explanation:** This limit is enforced for performance and storage reasons. Overly large internal data structures (resulting from documents with too many fields) lead to overly large databases on disk, and slower search performance.
## Maximum number of documents in an index
**Limitation:** An index can contain no more than 4,294,967,296 documents.
**Explanation:** This is the largest possible value for a 32-bit unsigned integer. Since Meilisearch's engine uses unsigned integers to identify documents internally, this is the maximum number of documents that can be stored in an index.
## Maximum number of concurrent search requests
**Limitation:** Meilisearch handles a maximum of 1000 concurrent search requests.
**Explanation:** This limit exists to prevent Meilisearch from queueing an unlimited number of requests and potentially consuming an unbounded amount of memory. If Meilisearch receives a new request when the queue is already full, it drops a random search request and returns a 503 `too_many_search_requests` error with a `Retry-After` header set to 10 seconds. Configure this limit with [`--experimental-search-queue-size`](/docs/resources/self_hosting/configuration/overview).
## Length of primary key values
**Limitation:** Primary key values are limited to 511 bytes.
**Explanation:** Meilisearch stores primary key values as LMDB keys, a data type whose size is limited to 511 bytes. If a primary key value exceeds 511 bytes, the task containing these documents will fail.
## Length of individual `filterableAttributes` values
**Limitation:** Individual `filterableAttributes` values are limited to 468 bytes.
**Explanation:** Meilisearch stores `filterableAttributes` values as keys in LMDB. Meilisearch uses an internal key length limit of 500 bytes with a 32-byte margin reserved for metadata, resulting in a maximum facet value length of 468 bytes. Note that this only applies to individual values. For example, a `genres` attribute can contain any number of values such as `horror`, `comedy`, or `cyberpunk` as long as each one of them is smaller than 468 bytes.
## Maximum filter depth
**Limitation:** searches using the [`filter` search parameter](/docs/reference/api/search/search-with-post#body-filter) may have a maximum filtering depth of 200.
**Explanation:** mixing and alternating `AND` and `OR` operators filters creates nested logic structures. Excessive nesting can lead to stack overflow.
### Example
The following filter is composed of a number of filter expressions. Since these statements are all chained with `OR` operators, there is no nesting:
```sql theme={null}
genre = "romance" OR genre = "horror" OR genre = "adventure"
```
Replacing `OR` with `AND` does not change the filter structure. The following filter's nesting level remains 1:
```sql theme={null}
genre = "romance" AND genre = "horror" AND genre = "adventure"
```
Nesting only occurs when alternating `AND` and `OR` operators. The following example fetches documents that either belong only to `user` `1`, or belong to users `2` and `3`:
```sql theme={null}
# AND is nested inside OR, creating a second level of nesting
user = 1 OR user = 2 AND user = 3
```
Adding parentheses can help visualizing nesting depth:
```sql theme={null}
# Depth 2
user = 1 OR (user = 2 AND user = 3)
# Depth 4
user = 1 OR (user = 2 AND (user = 3 OR (user = 4 AND user = 5)))
# Though this filter is longer, its nesting depth is still 2
user = 1 OR (user = 2 AND user = 3) OR (user = 4 AND user = 5) OR user = 6
```
## Size of integer fields
**Limitation:** Meilisearch can only exactly represent integers between -2⁵³ and 2⁵³.
**Explanation:** Meilisearch stores numeric values as double-precision floating-point numbers. This allows for greater precision and increases the range of magnitudes that Meilisearch can represent, but leads to inaccuracies in [values beyond certain thresholds](https://en.wikipedia.org/wiki/Double-precision_floating-point_format#Precision_limitations_on_integer_values).
## Maximum number of results per search
**Limitation:** By default, Meilisearch returns up to 1000 documents per search.
**Explanation:** Meilisearch limits the maximum amount of returned search results to protect your database from malicious scraping. You may change this by using the `maxTotalHits` property of the [pagination index settings](/docs/reference/api/settings/update-pagination). `maxTotalHits` only applies to the [search route](/docs/reference/api/search/search-with-post) and has no effect on the [get documents with POST](/docs/reference/api/documents/list-documents-with-post) and [get documents with GET](/docs/reference/api/documents/list-documents-with-get) endpoints.
## Large datasets and internal errors
**Limitation:** Meilisearch might throw an internal error when indexing large batches of documents.
**Explanation:** Indexing a large batch of documents, such as a JSON file over 3.5GB in size, can result in Meilisearch opening too many file descriptors. Depending on your machine, this might reach your system's default resource usage limits and trigger an internal error. Use [`ulimit`](https://www.ibm.com/docs/en/aix/7.1?topic=u-ulimit-command) or a similar tool to increase resource consumption limits before running Meilisearch. For example, call `ulimit -Sn 3000` in a UNIX environment to raise the number of allowed open file descriptors to 3000.
## Maximum database size
**Limitation:** Meilisearch supports a maximum index size of around 80TiB on Linux environments. For performance reasons, Meilisearch recommends keeping indexes under 2TiB.
**Explanation:** Meilisearch can accommodate indexes of any size as long the combined size of active databases is below the maximum virtual address space the OS devotes to a single process. On 64-bit Linux, this limit is approximately 80TiB.
## Maximum task database size
**Limitation:** Meilisearch supports a maximum task database size of 20GiB.
**Explanation:** Depending on your setup, 20GiB should correspond to 10M to 30M tasks. Once the task database contains over 1M entries (roughly 1GiB on average), Meilisearch tries to automatically delete finished tasks while continuing to enqueue new tasks as usual. This ensures the task database does not use an excessive amount of resources. If your database reaches the 20GiB limit, Meilisearch will log a warning indicating the engine is not working properly and refuse to enqueue new tasks.
## Maximum number of indexes in an instance
**Limitation:** Meilisearch can accommodate an arbitrary number of indexes as long as their size does not exceed 2TiB. When dealing with larger indexes, Meilisearch can accommodate up to 20 indexes as long as their combined size does not exceed the OS's virtual address space limit.
**Explanation:** While Meilisearch supports an arbitrary number of indexes under 2TiB, accessing hundreds of different databases in short periods of time might lead to decreased performance and should be avoided when possible.
## Facet Search limitation
**Limitation:** When [searching for facet values](/docs/reference/api/facet-search/search-for-facet-values), Meilisearch returns a maximum of 100 facets.
**Explanation:** the limit to the maximum number of returned facets has been implemented to offer a good balance between usability and comprehensive results. Facet search allows users to filter a large list of facets so they may quickly find categories relevant to their query. This is different from searching through an index of documents. Faceting index settings such as the `maxValuesPerFacet` limit do not impact facet search and only affect queries searching through documents.
# Language
Source: https://www.meilisearch.com/docs/resources/help/language
Meilisearch is compatible with datasets in any language. It features optimized tokenization for many language families and supports multilingual semantic search through embedding models.
Meilisearch is multilingual and works with datasets in any language. Its tokenizer, [Charabia](https://github.com/meilisearch/charabia), provides optimized segmentation and normalization for a wide range of languages and scripts.
## Supported languages
The following table lists all languages and scripts with dedicated tokenization support in Charabia:
| Language / Script | Segmentation | Normalization |
| --------------------------------------------------------------- | --------------------------------------- | --------------------------------------------------------- |
| **Latin** (English, French, Spanish, Italian, Portuguese, etc.) | CamelCase segmentation | Decomposition, lowercase, nonspacing-marks removal |
| **German** | CamelCase + compound word decomposition | Same as Latin |
| **Swedish** | Specialized normalization | Decomposition, lowercase |
| **Greek** | Default | Decomposition, lowercase, final sigma handling |
| **Cyrillic / Georgian** (Russian, Ukrainian, Bulgarian, etc.) | Default | Decomposition, lowercase |
| **Armenian** | Default | Decomposition, lowercase |
| **Arabic** | Article (ال) segmentation | Decomposition, digit conversion, nonspacing-marks removal |
| **Persian** | Specialized segmentation | Decomposition, normalization |
| **Hebrew** | Default | Decomposition, nonspacing-marks removal |
| **Turkish** | Default | Specialized case folding (dotted/dotless i) |
| **Chinese (CMN)** | jieba-based dictionary segmentation | Decomposition, kvariant conversion |
| **Japanese** | lindera IPA dictionary segmentation | Decomposition |
| **Korean** | lindera KO dictionary segmentation | Decomposition |
| **Thai** | Dictionary-based segmentation | Decomposition, nonspacing-marks removal |
| **Khmer** | Dictionary-based segmentation | Decomposition |
Languages not listed above still work with Meilisearch. Any language that uses whitespace to separate words benefits from the default Latin pipeline. Results may be less relevant for unlisted languages that do not use spaces between words.
We aim to provide global language support, and your feedback helps us move closer to that goal. If you notice inconsistencies in your search results or the way your documents are processed, please [open an issue in the Meilisearch repository](https://github.com/meilisearch/meilisearch/issues/new/choose).
[Read more about our tokenizer](/docs/capabilities/indexing/advanced/tokenization)
## Multilingual hybrid search
Meilisearch's keyword-based search relies on Charabia for tokenization, but [hybrid search](/docs/capabilities/hybrid_search/getting_started) and [semantic search](/docs/capabilities/hybrid_search/overview) use embedding models that can handle languages independently of the tokenizer.
Many embedding providers offer multilingual models that work across 100+ languages out of the box:
| Provider | Multilingual model | Dimensions |
| ---------------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------ |
| [Cohere](/docs/capabilities/hybrid_search/how_to/configure_cohere_embedder) | `embed-v4.0` | 256, 512, 1,024, or 1,536 |
| [Cohere](/docs/capabilities/hybrid_search/how_to/configure_cohere_embedder) | `embed-multilingual-v3.0` | 1,024 |
| [Voyage AI](/docs/capabilities/hybrid_search/providers/voyage) | `voyage-4` | 256, 512, 1,024, or 2,048 |
| [Jina](/docs/capabilities/hybrid_search/providers/jina) | `jina-embeddings-v4` | 128, 256, 512, 1,024, or 2,048 |
| [AWS Bedrock](/docs/capabilities/hybrid_search/providers/bedrock) | `cohere.embed-v4:0` | 256, 512, 1,024, or 1,536 |
| [Hugging Face](/docs/capabilities/hybrid_search/providers/huggingface) | `sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2` | 384 |
Using a multilingual embedding model allows you to:
* **Search across languages**: a query in English can match documents written in French, German, or Japanese.
* **Simplify multilingual indexing**: instead of creating one index per language, a single index with a multilingual embedder can serve multiple languages.
* **Complement keyword search**: combine Charabia's keyword tokenization with semantic embeddings in hybrid search for the best of both approaches.
For multilingual datasets, consider using [hybrid search](/docs/capabilities/hybrid_search/getting_started) with a multilingual embedder alongside [localized attributes](/docs/reference/api/settings/get-localizedattributes) for keyword matching. This gives you accurate tokenization per language for keyword search and cross-language understanding for semantic search.
For guidance on structuring multilingual datasets, see [Handling multilingual datasets](/docs/capabilities/indexing/how_to/handle_multilingual_data).
## Improving our language support
While we have employees from all over the world at Meilisearch, we don't speak every language. We rely almost entirely on feedback from external contributors to understand how our engine is performing across different languages.
If you'd like to request optimized support for a language, please upvote the related [discussion in our product repository](https://github.com/meilisearch/product/discussions?discussions_q=label%3Ascope%3Atokenizer+) or [open a new one](https://github.com/meilisearch/product/discussions/new?category=feedback-feature-proposal) if it doesn't exist.
If you'd like to help by developing a tokenizer pipeline yourself: first of all, thank you! We recommend that you take a look at the [tokenizer contribution guide](https://github.com/meilisearch/charabia/blob/main/CONTRIBUTING.md) before making a PR.
## FAQ
### What do you mean when you say Meilisearch offers *optimized* support for a language?
Optimized support for a language means Meilisearch has implemented internal processes specifically tailored to parsing that language, leading to more relevant results. This includes specialized segmentation (how text is split into words) and normalization (how characters are standardized for matching).
### My language does not use whitespace to separate words. Can I still use Meilisearch?
Yes. For keyword search, results may be less relevant than for fully optimized languages. However, you can use [hybrid search](/docs/capabilities/hybrid_search/getting_started) with a multilingual embedding model to get strong semantic results regardless of tokenization support.
### My language does not use the Roman alphabet. Can I still use Meilisearch?
Yes. Charabia supports many non-Latin scripts including Cyrillic, Greek, Arabic, Hebrew, Armenian, Thai, Chinese, Japanese, and Korean. Multilingual embedding models also work across all writing systems.
### Does Meilisearch plan to support additional languages in the future?
Yes, we definitely do. The more feedback we get from native speakers, the easier it is for us to understand how to improve performance for those languages. Similarly, the more requests we get to improve support for a specific language, the more likely we are to devote resources to that project.
# Official SDKs and libraries
Source: https://www.meilisearch.com/docs/resources/help/sdks
Meilisearch SDKs are available in many popular programming languages and frameworks. Consult this page for a full list of officially supported libraries.
## AI
Meilisearch provides an [MCP server](/docs/guides/ai/mcp) for integrating Meilisearch with LLM clients and IDEs.
MCP server to connect to your Meilisearch server
## Client SDKs
TypeScript client
Typed PHP client
Python client
Ruby client
Java client
Go client
.NET client
Dart client
## Framework SDKs
Official Laravel Scout integration
Gem for Ruby on Rails
## Platform SDKs
Sync your Strapi v5 collections to Meilisearch
Sync your Firebase collections to Meilisearch
## Front-end SDKs
Meilisearch provides connectors to integrate with Algolia's open-source search UI libraries.
InstantSearch connector
Autocomplete client
## Community-maintained SDKs
**Languages**
Rust client
Swift client
**Frameworks**
Bundle for Symfony
## Other tools
* [meilisearch-docsearch](https://github.com/tauri-apps/meilisearch-docsearch): a community-maintained scraper tool to automatically read the content of your documentation and store it into Meilisearch.
* [meilisearch-kubernetes](https://github.com/meilisearch/meilisearch-kubernetes): Kubernetes Helm charts and manifests
## Contributing
All Meilisearch integrations are open-source. We're proud that some of our libraries were kickstarted and are still maintained by external contributors! ♥️
If you'd like to contribute, check out the issues on the GitHub repositories. For more information [consult these guidelines](https://github.com/meilisearch/integrations-guides).
# Telemetry
Source: https://www.meilisearch.com/docs/resources/help/telemetry
Meilisearch collects anonymized data from users in order to improve our product. Consult this page for an exhaustive list of collected data and instructions on how to deactivate telemetry.
Meilisearch collects anonymized data from users in order to improve our product. This can be [deactivated at any time](#how-to-disable-data-collection), and any data that has already been collected can be [deleted on request](#how-to-delete-all-collected-data).
## What tools do we use to collect and visualize data?
We use [Segment](https://segment.com/), a platform for data collection and management, to collect usage data. We then feed that data into [Amplitude](https://amplitude.com/), a tool for graphing and highlighting data, so that we can build visualizations according to our needs.
## What kind of data do we collect?
Our data collection is focused on the following categories:
* **System** metrics, such as the technical specs of the device running Meilisearch, the software version, and the OS
* **Performance** metrics, such as the success rate of search requests and the average latency
* **Usage** metrics, aimed at evaluating our newest features. These change with each new version
See below for the [complete list of metrics we currently collect](#exhaustive-list-of-all-collected-data).
**We will never:**
* Identify or track users
* Collect personal information such as IP addresses, email addresses, or website URLs
* Store data from documents added to a Meilisearch instance
## Why collect telemetry data?
We collect telemetry data for only two reasons: so that we can improve our product, and so that we can continue working on this project full-time.
In order to create a better product, we need reliable quantitative information. The data we collect helps us fix bugs, evaluate the success of features, and better understand our users' needs.
We also need to prove that people are actually using Meilisearch. Usage metrics help us justify our existence to investors so that we can keep this project alive.
## Why should you trust us?
**Don't trust us, hold us accountable.** We feel that it is understandable, and in fact wise, to be distrustful of tech companies when it comes to your private data. That is why we attempt to maintain [complete transparency about our data collection](#exhaustive-list-of-all-collected-data), provide an [opt-out](#how-to-disable-data-collection), and enable users to [request the deletion of all their collected data](#how-to-delete-all-collected-data) at any time. In the absence of global data protection laws, we believe that this is the only ethical way to approach data collection.
No company is perfect. If you ever feel that we are being anything less than 100% transparent or collecting data that is infringing on your personal privacy, please let us know by emailing our dedicated account: [privacy@meilisearch.com](mailto:privacy@meilisearch.com). Similarly, if you discover a data rights initiative or data protection tool that you think is relevant to us, please share it. We are passionate about this subject and take it very seriously.
## How to disable data collection
Data collection can be disabled at any time by setting a command-line option or environment variable, then restarting the Meilisearch instance.
```bash theme={null}
meilisearch --no-analytics
```
```bash theme={null}
export MEILI_NO_ANALYTICS=true
meilisearch
```
```bash theme={null}
# First, open /etc/systemd/system/meilisearch.service with a text editor:
nano /etc/systemd/system/meilisearch.service
# Then add --no-analytics at the end of the command in ExecStart
# Don't forget to save and quit!
# Finally, run the following two commands:
systemctl daemon-reload
systemctl restart meilisearch
```
For more information about configuring Meilisearch, read our [configuration reference](/docs/resources/self_hosting/configuration/overview).
## How to delete all collected data
We, the Meilisearch team, provide an email address so that users can request the complete removal of their data from all of our tools.
To do so, send an email to [privacy@meilisearch.com](mailto:privacy@meilisearch.com) containing the unique identifier generated for your Meilisearch installation (`Instance UID` when launching Meilisearch). Any questions regarding the management of the data we collect can also be sent to this email address.
## Exhaustive list of all collected data
Whenever an event is triggered that collects some piece of data, Meilisearch does not send it immediately. Instead, it bundles it with other data in a batch of up to `500kb`. Batches are sent either every hour, or after reaching `500kb`, whichever occurs first. This is done in order to improve performance and reduce network traffic.
This list is liable to change with every new version of Meilisearch. It's not because we're trying to be sneaky! It's because when we add new features we need to collect additional data points to see how they perform.
| Metric name | Description | Example |
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `context.app.version` | Meilisearch version number | 1.3.0 |
| `infos.env` | Value of `--env`/`MEILI_ENV` | production |
| `infos.db_path` | `true` if `--db-path`/`MEILI_DB_PATH` is specified | true |
| `infos.import_dump` | `true` if `--import-dump` is specified | true |
| `infos.dump_dir` | `true` if `--dump-dir`/`MEILI_DUMP_DIR` is specified | true |
| `infos.ignore_missing_dump` | `true` if `--ignore-missing-dump` is activated | true |
| `infos.ignore_dump_if_db_exists` | `true` if `--ignore-dump-if-db-exists` is activated | true |
| `infos.import_snapshot` | `true` if `--import-snapshot` is specified | true |
| `infos.schedule_snapshot` | Value of `--schedule_snapshot`/`MEILI_SCHEDULE_SNAPSHOT` if set, otherwise `None` | 86400 |
| `infos.snapshot_dir` | `true` if `--snapshot-dir`/`MEILI_SNAPSHOT_DIR` is specified | true |
| `infos.ignore_missing_snapshot` | `true` if `--ignore-missing-snapshot` is activated | true |
| `infos.ignore_snapshot_if_db_exists` | `true` if `--ignore-snapshot-if-db-exists` is activated | true |
| `infos.http_addr` | `true` if `--http-addr`/`MEILI_HTTP_ADDR` is specified | true |
| `infos.http_payload_size_limit` | Value of `--http-payload-size-limit`/`MEILI_HTTP_PAYLOAD_SIZE_LIMIT` in bytes | 336042103 |
| `infos.log_level` | Value of `--log-level`/`MEILI_LOG_LEVEL` | debug |
| `infos.max_indexing_memory` | Value of `--max-indexing-memory`/`MEILI_MAX_INDEXING_MEMORY` in bytes | 336042103 |
| `infos.max_indexing_threads` | Value of `--max-indexing-threads`/`MEILI_MAX_INDEXING_THREADS` in integer | 4 |
| `infos.log_level` | Value of `--log-level`/`MEILI_LOG_LEVEL` | debug |
| `infos.ssl_auth_path` | `true` if `--ssl-auth-path`/`MEILI_SSL_AUTH_PATH` is specified | false |
| `infos.ssl_cert_path` | `true` if `--ssl-cert-path`/`MEILI_SSL_CERT_PATH` is specified | false |
| `infos.ssl_key_path` | `true` if `--ssl-key-path`/`MEILI_SSL_KEY_PATH` is specified | false |
| `infos.ssl_ocsp_path` | `true` if `--ssl-ocsp-path`/`MEILI_SSL_OCSP_PATH` is specified | false |
| `infos.ssl_require_auth` | Value of `--ssl-require-auth`/`MEILI_SSL_REQUIRE_AUTH` as a boolean | false |
| `infos.ssl_resumption` | `true` if `--ssl-resumption`/`MEILI_SSL_RESUMPTION` is specified | false |
| `infos.ssl_tickets` | `true` if `--ssl-tickets`/`MEILI_SSL_TICKETS` is specified | false |
| `system.distribution` | Distribution on which Meilisearch is launched | Arch Linux |
| `system.kernel_version` | Kernel version on which Meilisearch is launched | 5.14.10 |
| `system.cores` | Number of cores | 24 |
| `system.ram_size` | Total RAM capacity. Expressed in `KB` | 16777216 |
| `system.disk_size` | Total capacity of the largest disk. Expressed in `Bytes` | 1048576000 |
| `system.server_provider` | Value of `MEILI_SERVER_PROVIDER` environment variable | AWS |
| `stats.database_size` | Database size. Expressed in `Bytes` | 2621440 |
| `stats.indexes_number` | Number of indexes | 2 |
| `start_since_days` | Number of days since instance was launched | 365 |
| `user_agent` | User-agent header encountered during API calls | \["Meilisearch Ruby (2.1)", "Ruby (3.0)"] |
| `requests.99th_response_time` | Highest latency from among the fastest 99% of successful search requests | 57ms |
| `requests.total_succeeded` | Total number of successful requests | 3456 |
| `requests.total_failed` | Total number of failed requests | 24 |
| `requests.total_received` | Total number of received search requests | 3480 |
| `requests.total_degraded` | Total number of searches canceled after reaching search time cut-off | 100 |
| `requests.total_used_negative_operator` | Count searches using either a negative word or a negative phrase operator | 173 |
| `sort.with_geoPoint` | `true` if the sort rule `_geoPoint` is specified | true |
| `sort.avg_criteria_number` | Average number of sort criteria among all search requests containing the `sort` parameter | 2 |
| `filter.with_geoBoundingBox` | `true` if the filter rule `_geoBoundingBox` is specified | false |
| `filter.with_geoRadius` | `true` if the filter rule `_geoRadius` is specified | false |
| `filter.most_used_syntax` | Most used filter syntax among all search requests containing the `filter` parameter | string |
| `filter.on_vectors` | `true` if the filter rule includes `_vector` | false |
| `q.max_terms_number` | Highest number of terms given for the `q` parameter | 5 |
| `pagination.max_limit` | Highest value given for the `limit` parameter | 60 |
| `pagination.max_offset` | Highest value given for the `offset` parameter | 1000 |
| `formatting.max_attributes_to_retrieve` | Maximum number of attributes to retrieve | 100 |
| `formatting.max_attributes_to_highlight` | Maximum number of attributes to highlight | 100 |
| `formatting.highlight_pre_tag` | `true` if `highlightPreTag` is specified | false |
| `formatting.highlight_post_tag` | `true` if `highlightPostTag` is specified | false |
| `formatting.max_attributes_to_crop` | Maximum number of attributes to crop | 100 |
| `formatting.crop_length` | `true` if `cropLength` is specified | false |
| `formatting.crop_marker` | `true` if `cropMarker` is specified | false |
| `formatting.show_matches_position` | `true` if `showMatchesPosition` is used in this batch | false |
| `facets.avg_facets_number` | Average number of facets | 10 |
| `primary_key` | Name of primary key when explicitly set. Otherwise `null` | id |
| `payload_type` | All values encountered in the `Content-Type` header, including invalid ones | \["application/json", "text/plain", "application/x-ndjson"] |
| `index_creation` | `true` if a document addition or update request triggered index creation | true |
| `ranking_rules.words_position` | Position of the `words` ranking rule if any, otherwise `null` | 1 |
| `ranking_rules.typo_position` | Position of the `typo` ranking rule if any, otherwise `null` | 2 |
| `ranking_rules.proximity_position` | Position of the `proximity` ranking rule if any, otherwise `null` | 3 |
| `ranking_rules.attribute_position` | Position of the `attribute` ranking rule if any, otherwise `null` | 4 |
| `ranking_rules.attribute_rank_position` | Position of the `attributeRank` ranking rule if any, otherwise `null` | 5 |
| `ranking_rules.attribute_position_position` | Position of the `wordPosition` ranking rule if any, otherwise `null` | 6 |
| `ranking_rules.sort_position` | Position of the `sort` ranking rule | 7 |
| `ranking_rules.exactness_position` | Position of the `exactness` ranking rule if any, otherwise `null` | 8 |
| `ranking_rules.values` | A string representing the ranking rules without the custom asc-desc rules | "words, typo, attributeRank, sort, wordPosition, exactness" |
| `sortable_attributes.total` | Number of sortable attributes | 3 |
| `sortable_attributes.has_geo` | `true` if `_geo` is set as a sortable attribute | true |
| `filterable_attributes.total` | Number of filterable attributes | 3 |
| `filterable_attributes.has_geo` | `true` if `_geo` is set as a filterable attribute | false |
| `filterable_attributes.has_patterns` | `true` if `filterableAttributes` uses `attributePatterns` | true |
| `searchable_attributes.total` | Number of searchable attributes | 4 |
| `searchable_attributes.with_wildcard` | `true` if `*` is specified as a searchable attribute | false |
| `per_task_uid` | `true` if a `uids` is used to fetch a particular task resource | true |
| `filtered_by_uid` | `true` if tasks are filtered by the `uids` query parameter | false |
| `filtered_by_index_uid` | `true` if tasks are filtered by the `indexUids` query parameter | false |
| `filtered_by_type` | `true` if tasks are filtered by the `types` query parameter | false |
| `filtered_by_status` | `true` if tasks are filtered by the `statuses` query parameter | false |
| `filtered_by_canceled_by` | `true` if tasks are filtered by the `canceledBy` query parameter | false |
| `filtered_by_before_enqueued_at` | `true` if tasks are filtered by the `beforeEnqueuedAt` query parameter | false |
| `filtered_by_after_enqueued_at` | `true` if tasks are filtered by the `afterEnqueuedAt` query parameter | false |
| `filtered_by_before_started_at` | `true` if tasks are filtered by the `beforeStartedAt` query parameter | false |
| `filtered_by_after_started_at` | `true` if tasks are filtered by the `afterStartedAt` query parameter | false |
| `filtered_by_before_finished_at` | `true` if tasks are filtered by the `beforeFinishedAt` query parameter | false |
| `filtered_by_after_finished_at` | `true` if tasks are filtered by the `afterFinishedAt` query parameter | false |
| `typo_tolerance.enabled` | `true` if typo tolerance is enabled | true |
| `typo_tolerance.disable_on_attributes` | `true` if at least one value is defined for `disableOnAttributes` | false |
| `typo_tolerance.disable_on_words` | `true` if at least one value is defined for `disableOnWords` | false |
| `typo_tolerance.min_word_size_for_typos.one_typo` | The defined value for the `minWordSizeForTypos.oneTypo` parameter | 5 |
| `typo_tolerance.min_word_size_for_typos.two_typos` | The defined value for the `minWordSizeForTypos.twoTypos` parameter | 9 |
| `pagination.max_total_hits` | The defined value for the `pagination.maxTotalHits` property | 1000 |
| `faceting.max_values_per_facet` | The defined value for the `faceting.maxValuesPerFacet` property | 100 |
| `distinct_attribute.set` | `true` if a field name is specified | false |
| `distinct` | `true` if a distinct was specified in an aggregated list of requests | true |
| `proximity_precision.set` | `true` if the setting has been manually set. | false |
| `proximity_precision.value` | `byWord` or `byAttribute`. | byWord |
| `facet_search.set` | `facetSearch` has been changed by the user | true |
| `facet_search.value` | `facetSearch` value set by the user | true |
| `prefix_search.set` | `prefixSearch` has been changed by the user | true |
| `prefix_search.value` | `prefixSearch` value set by the user | indexingTime |
| `displayed_attributes.total` | Number of displayed attributes | 3 |
| `displayed_attributes.with_wildcard` | `true` if `*` is specified as a displayed attribute | false |
| `stop_words.total` | Number of stop words | 3 |
| `separator_tokens.total` | Number of separator tokens | 3 |
| `non_separator_tokens.total` | Number of non-separator tokens | 3 |
| `dictionary.total` | Number of words in the dictionary | 3 |
| `synonyms.total` | Number of synonyms | 3 |
| `per_index_uid` | `true` if the `uid` is used to fetch an index stat resource | false |
| `searches.avg_search_count` | The average number of search queries received per call for the aggregated event | 4.2 |
| `searches.total_search_count` | The total number of search queries received for the aggregated event | 16023 |
| `indexes.avg_distinct_index_count` | The average number of queried indexes received per call for the aggregated event | 1.2 |
| `indexes.total_distinct_index_count` | The total number of distinct index queries for the aggregated event | 6023 |
| `indexes.total_single_index` | The total number of calls when only one index is queried | 2007 |
| `matching_strategy.most_used_strategy` | Most used word matching strategy | last |
| `infos.with_configuration_file` | `true` if the instance is launched with a configuration file | false |
| `infos.experimental_composite_embedders` | `true` if the `compositeEmbedders` feature is set to `true` for this instance | false |
| `infos.experimental_contains_filter` | `true` if the `containsFilter` experimental feature is enabled | false |
| `infos.experimental_edit_documents_by_function` | `true` if the `editDocumentsByFunction` experimental feature is enabled | false |
| `infos.experimental_enable_metrics` | `true` if `--experimental-enable-metrics` is specified at launch | false |
| `infos.experimental_embedding_cache_entries` | Size of configured embedding cache | 100 |
| `infos.experimental_multimodal` | `true` when multimodal search feature is enabled | true |
| `infos.experimental_no_edition_2024_for_settings` | `true` if instance disabled new indexer | false |
| `infos.experimental_reduce_indexing_memory_usage` | `true` if `--experimental-reduce-indexing-memory-usage` is specified at launch | false |
| `infos.experimental_logs_mode` | `human` or `json` depending on the value specified | human |
| `infos.experimental_enable_logs_route` | `true` if `--experimental-enable-logs-route` is specified at launch | false |
| `infos.gpu_enabled` | `true` if Meilisearch was compiled with CUDA support | false |
| `swap_operation_number` | Number of swap operations | 2 |
| `pagination.most_used_navigation` | Most used search results navigation | estimated |
| `per_document_id` | `true` if the `DELETE /indexes/:indexUid/documents/:documentUid` endpoint was used | false |
| `per_filter` | `true` if `POST /indexes/:indexUid/documents/fetch`, `GET /indexes/:indexUid/documents/`, or `POST /indexes/:indexUid/documents/delete` endpoints were used | false |
| `clear_all` | `true` if `DELETE /indexes/:indexUid/documents` endpoint was used | false |
| `per_batch` | `true` if the `POST /indexes/:indexUid/documents/delete-batch` endpoint was used | false |
| `facets.total_distinct_facet_count` | Total number of distinct facets queried for the aggregated event | false |
| `facets.additional_search_parameters_provided` | `true` if additional search parameters were provided for the aggregated event | false |
| `faceting.sort_facet_values_by_star_count` | `true` if all fields are set to be sorted by count | false |
| `faceting.sort_facet_values_by_total` | The number of different values that were set | 10 |
| `scoring.show_ranking_score` | `true` if `showRankingScore` used in the aggregated event | true |
| `scoring.show_ranking_score_details` | `true` if `showRankingScoreDetails` was used in the aggregated event | true |
| `scoring.ranking_score_threshold` | `true` if rankingScoreThreshold was specified in an aggregated list of requests | true |
| `attributes_to_search_on.total_number_of_uses` | Total number of queries where `attributesToSearchOn` is set | 5 |
| `vector.max_vector_size` | Highest number of dimensions given for the `vector` parameter in this batch | 1536 |
| `vector.retrieve_vectors` | `true` if the retrieve\_vectors parameter has been used in this batch. | false |
| `hybrid.enabled` | `true` if hybrid search been used in the aggregated event | true |
| `hybrid.semantic_ratio` | `true` if semanticRatio was used in this batch, otherwise false | false |
| `hybrid.total_media` | Aggregated number of search requests where `media` is not `null` | 42 |
| `embedders.total` | Numbers of defined embedders | 2 |
| `embedders.sources` | An array representing the different provided sources | \["huggingFace", "userProvided"] |
| `embedders.document_template_used` | A boolean indicating if one of the provided embedders has a custom template defined | true |
| `embedders.document_template_max_bytes` | A value indicating the largest value for document TemplateMaxBytes across all embedder | 400 |
| `embedders.binary_quantization_used` | `true` if the user updated the binary quantized field of the embedded settings | false |
| `infos.task_queue_webhook` | `true` if the instance is launched with a task queue webhook | false |
| `infos.experimental_search_queue_size` | Size of the search queue | 750 |
| `infos.upgrade_db` | `true` if instance is launched with the `--upgrade-db` parameter | true |
| `locales` | List of locales used with `/search` and `/settings` routes | \["fra", "eng"] |
| `federation.use_federation` | `true` when at least one multi-search request contains a top-level federation object | false |
| `network_has_self` | `true` if the network object has a non-null self field | true |
| `network_size` | Number of declared remotes | 0 |
| `network` | `true` when the network experimental feature is enabled | true |
| `experimental_network` | `true` when the network experimental feature is enabled | true |
| `remotes.total_distinct_remote_count` | Sum of the number of distinct remotes appearing in each search request of the aggregate | 48 |
| `remotes.avg_distinct_remote_count` | Average number of distinct remotes appearing in a search request of the aggregate | 2.33 |
| `multimodal` | `true` when multimodal search is enabled via the `/experimental-features` route | true |
| `export.total_received` | Number of exports received in this batch | `152` |
| `export.has_api_key` | Number of exports with an API Key set | `89` |
| `export.avg_index_patterns` | Average number of index patterns set per export | `3.2` |
| `export.avg_patterns_with_filter` | Average number of index patterns with filters per export | `1.7` |
| `export.avg_payload_size` | Average payload size per export | `512` |
| `webhooks_created` | Number of webhooks created in an instance | `2` |
| `webhooks.updated` | Number of times all webhooks in an instance have been updated | `5` |
| `with_vector_filter` | `true` when a document fetch request used a vector filter | `false` |
# Versioning policy
Source: https://www.meilisearch.com/docs/resources/help/versioning
This article describes the system behind Meilisearch's SDK and engine version numbering and compatibility.
This article describes the system behind Meilisearch's version numbering, compatibility between Meilisearch versions, and how Meilisearch version numbers relate to SDK and documentation versions.
## Engine versioning
Release versions follow the MAJOR.MINOR.PATCH format and adhere to the [Semantic Versioning 2.0.0 convention](https://semver.org/#semantic-versioning-200).
* MAJOR versions contain changes that break compatibility between releases
* MINOR versions introduce new features that are backwards compatible
* PATCH versions only contain high-priority bug fixes and security updates
### Release schedule
Meilisearch releases new versions between four and six times a year. This number does not include PATCH releases.
### Support for previous versions
Meilisearch only maintains the latest engine release. Currently, there are no EOL (End of Life) or LTS (Long-Term Support) policies.
Consult the [engine versioning policy](https://github.com/meilisearch/engine-team/blob/main/resources/versioning-policy.md) for more information.
## SDK versioning
Meilisearch version numbers have no relationship to SDK version numbers. SDKs follow their own release schedules and must address issues beyond compatibility with Meilisearch.
When using an SDK, always consult its repository README, release description, and any dedicated documentation to determine which Meilisearch versions and features it supports.
## Documentation versioning
This Meilisearch documentation website follows the latest Meilisearch version. We do not maintain documentation for past releases.
# Bucket sort
Source: https://www.meilisearch.com/docs/resources/internals/bucket_sort
How Meilisearch uses bucket sort to rank search results through sequential ranking rules.
Meilisearch uses **bucket sort** to rank search results. This algorithm distributes documents into buckets based on ranking rules, then recursively sorts within each bucket using subsequent rules.
## How bucket sort works in Meilisearch
When you search, Meilisearch doesn't score documents with a single number. Instead, it applies [ranking rules](/docs/capabilities/full_text_search/relevancy/ranking_rules) sequentially, sorting documents into buckets at each step.
### Example: Searching for "Badman dark knight returns"
**Step 1: Apply the `words` rule**
The first ranking rule (`words`) sorts documents by how many query words they contain:
| Bucket | Matches | Documents |
| ------ | ----------- | --------------------------------- |
| 1 | All 4 words | "Batman: The Dark Knight Returns" |
| 2 | 3 words | "Batman: The Dark Knight" |
| 3 | 2 words | ... |
| 4 | 1 word | "Angel and the Badman" |
**Step 2: Apply the `typo` rule within buckets**
If a bucket contains multiple documents, the next rule (`typo`) breaks ties. For example, in the 1-word bucket where "Badman" appears:
| Bucket | Typos | Documents |
| ------ | ------- | --------------------------------------- |
| 4.1 | 0 typos | Documents containing "Badman" exactly |
| 4.2 | 1 typo | Documents corrected "Badman" → "Batman" |
This continues recursively until all buckets contain single documents or all ranking rules are exhausted.
## Why bucket sort?
Bucket sort offers several advantages for search ranking:
1. **Flexibility**: Different sorting algorithms can be applied within individual buckets
2. **Configurable priority**: You control which criteria matter most by reordering rules
3. **Efficient tie-breaking**: Only documents that tie on one rule need evaluation by the next
## Best and worst cases
| Case | Condition | Complexity |
| --------- | -------------------------------------- | ------------------------------------- |
| **Best** | Documents spread evenly across buckets | O(n+k) where n=documents, k=buckets |
| **Worst** | All documents in one bucket | Depends on the sorting algorithm used |
## Default ranking rules
Meilisearch applies these rules in order:
1. **words**: Documents containing more query words rank higher
2. **typo**: Documents with fewer typos rank higher
3. **proximity**: Documents where query words appear closer together rank higher
4. **attributeRank**: Documents matching in more important attributes rank higher
5. **sort**: User-defined sort order (if specified)
6. **wordPosition**: Documents with matches closer to the beginning of an attribute rank higher
7. **exactness**: Documents with exact matches rank higher
## Customizing ranking rules
You can reorder, add, or remove ranking rules:
```bash theme={null}
curl -X PUT "${MEILISEARCH_URL}/indexes/movies/settings/ranking-rules" \
-H "Authorization: Bearer ${MEILISEARCH_KEY}" \
-H "Content-Type: application/json" \
--data-binary '[
"words",
"typo",
"proximity",
"attributeRank",
"sort",
"wordPosition",
"exactness",
"release_date:desc"
]'
```
Adding `release_date:desc` as a custom rule means newer movies rank higher when all other factors are equal.
## Related concepts
* [Ranking rules](/docs/capabilities/full_text_search/relevancy/ranking_rules): Configure ranking behavior
* [Ranking score](/docs/capabilities/full_text_search/relevancy/ranking_score): Understanding search relevance scores
* [Custom ranking rules](/docs/capabilities/full_text_search/relevancy/custom_ranking_rules): Add business logic to ranking
# Concatenated and split queries
Source: https://www.meilisearch.com/docs/resources/internals/concat
When a query contains several terms, Meilisearch looks for both individual terms and their combinations.
## Concatenated queries
When your search contains several words, Meilisearch applies a concatenation algorithm to it.
When searching for multiple words, a search is also done on the concatenation of those words. When concatenation is done on a search query containing multiple words, it will concatenate the words following each other. Thus, the first and third words will not be concatenated without the second word.
### Example
A search on `The news paper` will also search for the following concatenated queries:
* `Thenews paper`
* `the newspaper`
* `Thenewspaper`
This concatenation is done on a **maximum of 3 words**.
## Split queries
When you do a search, it **applies the splitting algorithm to every word** (*string separated by a space*).
This consists of finding the most interesting place to separate the words and to create a parallel search query with this proposition.
This is achieved by finding the best frequency of the separate words in the dictionary of all words in the dataset. It will look out that both words have a minimum of interesting results, and not just one of them.
Split words are not considered as multiple words in a search query because they must stay next to each other.
### Example
On a search on `newspaper`, it will split into `news` and `paper` and not into `new` and `spaper`.
A document containing `news` and `paper` separated by other words will not be relevant to the search.
# Data types
Source: https://www.meilisearch.com/docs/resources/internals/datatypes
Learn about how Meilisearch handles different data types: strings, numerical values, booleans, arrays, and objects.
This article explains how Meilisearch handles the different types of data in your dataset.
**The behavior described here concerns only Meilisearch's internal processes** and can be helpful in understanding how the tokenizer works. Document fields remain unchanged for most practical purposes not related to Meilisearch's inner workings.
## String
String is the primary type for indexing data in Meilisearch. It enables to create the content in which to search. Strings are processed as detailed below.
String tokenization is the process of **splitting a string into a list of individual terms that are called tokens.**
A string is passed to a tokenizer and is then broken into separate string tokens. A token is a **word**.
### Tokenization
Tokenization relies on two main processes to identifying words and separating them into tokens: separators and dictionaries.
#### Separators
Separators are characters that indicate where one word ends and another word begins. In languages using the Latin alphabet, for example, words are usually delimited by white space. In Japanese, word boundaries are more commonly indicated in other ways, such as appending particles like `に` and `で` to the end of a word.
There are two kinds of separators in Meilisearch: soft and hard. Hard separators signal a significant context switch such as a new sentence or paragraph. Soft separators only delimit one word from another but do not imply a major change of subject.
The list below presents some of the most common separators in languages using the Latin alphabet:
* **Soft spaces** (distance: 1): whitespaces, quotes, `'-' | '_' | '\'' | ':' | '/' | '\\' | '@' | '"' | '+' | '~' | '=' | '^' | '*' | '#'`
* **Hard spaces** (distance: 8): `'.' | ';' | ',' | '!' | '?' | '(' | ')' | '[' | ']' | '{' | '}'| '|'`
For more separators, including those used in other writing systems like Cyrillic and Thai, [consult this exhaustive list](https://docs.rs/charabia/0.9.9/src/charabia/separators.rs.html#16-62).
#### Dictionaries
For the tokenization process, dictionaries are lists of groups of characters which should be considered as single term. Dictionaries are particularly useful when identifying words in languages like Japanese, where words are not always marked by separator tokens.
Meilisearch comes with a number of general-use dictionaries for its officially supported languages. When working with documents containing many domain-specific terms, such as a legal documents or academic papers, providing a [custom dictionary](/docs/reference/api/settings/get-dictionary) may improve search result relevancy.
### Distance
Distance plays an essential role in determining whether documents are relevant since [one of the ranking rules is the **proximity** rule](/docs/capabilities/full_text_search/relevancy/relevancy). The proximity rule sorts the results by increasing distance between matched query terms. Then, two words separated by a soft space are closer and thus considered **more relevant** than two words separated by a hard space.
After the tokenizing process, each word is indexed and stored in the global dictionary of the corresponding index.
### Examples
To demonstrate how a string is split by space, let's say you have the following string as an input:
```
"Bruce Willis,Vin Diesel"
```
In the example above, the distance between `Bruce` and `Willis` is equal to **1**. The distance between `Vin` and `Diesel` is also **1**. However, the distance between `Willis` and `Vin` is equal to **8**. The same calculations apply to `Bruce` and `Diesel` (10), `Bruce` and `Vin` (9), and `Willis` and `Diesel` (9).
Let's see another example. Given two documents:
```json theme={null}
[
{
"movie_id": "001",
"description": "Bruce.Willis"
},
{
"movie_id": "002",
"description": "Bruce super Willis"
}
]
```
When making a query on `Bruce Willis`, `002` will be the first document returned, and `001` will be the second one. This will happen because the proximity distance between `Bruce` and `Willis` is equal to **2** in the document `002`, whereas the distance between `Bruce` and `Willis` is equal to **8** in the document `001` since the full-stop character `.` is a hard space.
## Numeric
A numeric type (`integer`, `float`) is converted to a human-readable decimal number string representation. Numeric types can be searched as they are converted to strings.
You can add [custom ranking rules](/docs/capabilities/full_text_search/relevancy/custom_ranking_rules) to create an ascending or descending sorting rule on a given attribute that has a numeric value in the documents.
You can also create [filters](/docs/capabilities/filtering_sorting_faceting/getting_started). The `>`, `>=`, `<`, `<=`, and `TO` relational operators apply only to numerical values.
### Floating-point precision
Meilisearch stores all numeric values as 64-bit IEEE 754 floating-point numbers (`f64`). This format provides up to approximately 15 significant decimal digits of precision.
As a consequence, very large integers (beyond 2^53, or 9,007,199,254,740,992) may lose precision when stored in Meilisearch. If your dataset includes large integer identifiers or high-precision decimal values, consider storing them as strings instead to preserve their exact representation.
## Boolean
A Boolean value, which is either `true` or `false`, is received and converted to a lowercase human-readable text (`true` and `false`). Booleans can be searched as they are converted to strings.
## `null`
The `null` type can be pushed into Meilisearch but it **won't be taken into account for indexing**.
## Array
An array is an ordered list of values. These values can be of any type: number, string, boolean, object, or even other arrays.
Meilisearch flattens arrays and concatenates them into strings. Non-string values are converted as described in this article's previous sections.
### Example
The following input:
```json theme={null}
[
[
"Bruce Willis",
"Vin Diesel"
],
"Kung Fu Panda"
]
```
Will be processed as if all elements were arranged at the same level:
```json theme={null}
"Bruce Willis. Vin Diesel. Kung Fu Panda."
```
Once the above array has been flattened, it will be parsed exactly as explained in the [string example](/docs/resources/internals/datatypes#examples).
## Objects
When a document field contains an object, Meilisearch flattens it and brings the object's keys and values to the root level of the document itself.
Keep in mind that the flattened objects represented here are an intermediary snapshot of internal processes. When searching, the returned document will keep its original structure.
In the example below, the `patient_name` key contains an object:
```json theme={null}
{
"id": 0,
"patient_name": {
"forename": "Imogen",
"surname": "Temult"
}
}
```
During indexing, Meilisearch uses dot notation to eliminate nested fields:
```json theme={null}
{
"id": 0,
"patient_name.forename": "Imogen",
"patient_name.surname": "Temult"
}
```
Using dot notation, no information is lost when flattening nested objects, regardless of nesting depth. Dot notation also works with arrays of objects. For example, if a document contains `"items": [{"name": "foo"}, {"name": "bar"}]`, Meilisearch flattens this to `"items.name": ["foo", "bar"]`. You can use `items.name` when configuring searchable, filterable, or sortable attributes to access these nested values.
Imagine that the example document above includes an additional object, `address`, containing home and work addresses, each of which are objects themselves. After flattening, the document would look like this:
```json theme={null}
{
"id": 0,
"patient_name.forename": "Imogen",
"patient_name.surname": "Temult",
"address.home.street": "Largo Isarco, 2",
"address.home.postcode": "20139",
"address.home.city": "Milano",
"address.work.street": "Ca' Corner Della Regina, 2215",
"address.work.postcode": "30135",
"address.work.city": "Venezia"
}
```
Meilisearch's internal flattening process also eliminates nesting in arrays of objects. In this case, values are grouped by key. Consider the following document:
```json theme={null}
{
"id": 0,
"patient_name": "Imogen Temult",
"appointments": [
{
"date": "2022-01-01",
"doctor": "Jester Lavorre",
"ward": "psychiatry"
},
{
"date": "2019-01-01",
"doctor": "Dorian Storm"
}
]
}
```
After flattening, it would look like this:
```json theme={null}
{
"id": 0,
"patient_name": "Imogen Temult",
"appointments.date": [
"2022-01-01",
"2019-01-01"
],
"appointments.doctor": [
"Jester Lavorre",
"Dorian Storm"
],
"appointments.ward": [
"psychiatry"
]
}
```
Once all objects inside a document have been flattened, Meilisearch will continue processing it as described in the previous sections. For example, arrays will be flattened, and numeric and boolean values will be turned into strings.
### Nested document querying and subdocuments
Meilisearch has no concept of subdocuments and cannot perform nested document querying. In the previous example, the relationship between an appointment's date and doctor is lost when flattening the `appointments` array:
```json theme={null}
…
"appointments.date": [
"2022-01-01",
"2019-01-01"
],
"appointments.doctor": [
"Jester Lavorre",
"Dorian Storm"
],
…
```
This may lead to unexpected behavior during search. The following dataset shows two patients and their respective appointments:
```json theme={null}
[
{
"id": 0,
"patient_name": "Imogen Temult",
"appointments": [
{
"date": "2022-01-01",
"doctor": "Jester Lavorre"
}
]
},
{
"id": 1,
"patient_name": "Caleb Widowgast",
"appointments": [
{
"date": "2022-01-01",
"doctor": "Dorian Storm"
},
{
"date": "2023-01-01",
"doctor": "Jester Lavorre"
}
]
}
]
```
The following query returns patients `0` and `1`:
```sh theme={null}
curl \
-X POST 'MEILISEARCH_URL/indexes/clinic_patients/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "",
"filter": "(appointments.date = 2022-01-01 AND appointments.doctor = '\''Jester Lavorre'\'')"
}'
```
Meilisearch is unable to only return patients who had an appointment with `Jester Lavorre` in `2022-01-01`. Instead, it returns patients who had an appointment with `Jester Lavorre`, and patients who had an appointment in `2022-01-01`.
The best way to work around this limitation is reformatting your data. The above example could be fixed by merging appointment data in a new `appointmentsMerged` field so the relationship between appointment and doctor remains intact:
```json theme={null}
[
{
"id": 0,
"patient_name": "Imogen Temult",
"appointmentsMerged": [
"2022-01-01 Jester Lavorre"
]
},
{
"id": 1,
"patient_name": "Caleb Widowgast",
"appointmentsMerged": [
"2023-01-01 Jester Lavorre"
"2022-01-01 Dorian Storm"
]
}
]
```
### Updating object fields
Object fields cannot be partially updated. Updating an object field with either the `PUT` or `POST` routes with an object fully replaces that value and removes any omitted subfields. Dot notation is also not supported when updating a document.
## Reserved field: `_vectors`
When using [AI-powered search](/docs/capabilities/hybrid_search/overview), documents can contain a special `_vectors` field. This reserved field stores embedding data for one or more configured embedders.
The `_vectors` field is an object where each key corresponds to a configured embedder name. Values can use one of two formats:
**Simple format** (array of numbers):
```json theme={null}
{
"id": 1,
"title": "A great movie",
"_vectors": {
"my_embedder": [0.1, 0.2, 0.3, 0.4]
}
}
```
**Explicit format** (object with `embeddings` and `regenerate`):
```json theme={null}
{
"id": 1,
"title": "A great movie",
"_vectors": {
"my_embedder": {
"embeddings": [[0.1, 0.2, 0.3, 0.4]],
"regenerate": false
}
}
}
```
In the explicit format, `embeddings` is an array of arrays (supporting multiple embeddings per document), and `regenerate` controls whether Meilisearch should regenerate the embedding when the document is updated. Set `regenerate` to `false` when you provide your own embeddings and do not want Meilisearch to overwrite them.
The `_vectors` field is only relevant when you have configured at least one [embedder](/docs/reference/api/settings/update-embedders) for your index. If no embedder is configured, `_vectors` is treated as a regular field.
## Possible tokenization issues
Even if it behaves exactly as expected, the tokenization process may lead to counterintuitive results in some cases, such as:
```
"S.O.S"
"George R. R. Martin"
10,3
```
For the two strings above, the full stops `.` will be considered as hard spaces.
`10,3` will be broken into two strings (`10` and `3`) instead of being processed as a numeric type.
# Documents
Source: https://www.meilisearch.com/docs/resources/internals/documents
Documents are the individual items that make up a dataset. Each document is an object composed of one or more fields.
A document is an object composed of one or more fields. Each field consists of an **attribute** and its associated **value**. Documents function as containers for organizing data and are the basic building blocks of a Meilisearch database. To search for a document, you must first add it to an [index](/docs/resources/internals/indexes).
Nothing will be shared between two indexes if they contain the exact same document. Instead, both documents will be treated as different documents. Depending on the [index's settings](/docs/reference/api/settings/list-all-settings), the documents might have different sizes.
## Structure
### Important terms
* **Document**: an object which contains data in the form of one or more fields
* **[Field](#fields)**: a set of two data items that are linked together: an attribute and a value
* **Attribute**: the first part of a field. Acts as a name or description for its associated value
* **Value**: the second part of a field, consisting of data of any valid JSON type
* **[Primary Field](#primary-field)**: a special field that is mandatory in all documents. It contains the primary key and document identifier
## Fields
A **field** is a set of two data items linked together: an attribute and a value. Documents are made up of fields.
An **attribute** is a case-sensitive string that functions as a field's name and allows you to store, access, and describe data.
That data is the field's **value**. Every field has a data type dictated by its value. Every value must be a valid [JSON data type](https://www.w3schools.com/js/js_json_datatypes.asp).
If the value is a string, it **[can contain at most 65535 positions](/docs/resources/help/known_limitations#maximum-number-of-words-per-attribute)**. Words exceeding the 65535 position limit will be ignored.
If a field contains an object, Meilisearch flattens it during indexing using dot notation and brings the object's keys and values to the root level of the document itself. This flattened object is only an intermediary representation. You will get the original structure upon search. You can read more about this in our [dedicated guide](/docs/resources/internals/datatypes#objects).
With [ranking rules](/docs/capabilities/full_text_search/relevancy/ranking_rules), you can decide which fields are more relevant than others. For example, you may decide recent movies should be more relevant than older ones. You can also designate certain fields as displayed or searchable.
Some features require Meilisearch to reserve attributes. For example, to use [geosearch functionality](/docs/capabilities/geo_search/getting_started) your documents must include a `_geo` field.
Reserved attributes are always prefixed with an underscore (`_`).
### Displayed and searchable fields
By default, all fields in a document are both displayed and searchable. Displayed fields are contained in each matching document, while searchable fields are searched for matching query words.
You can modify this behavior using the [update settings endpoint](/docs/reference/api/settings/update-all-settings), or the respective update endpoints for [displayed attributes](/docs/reference/api/settings/update-displayedattributes), and [searchable attributes](/docs/reference/api/settings/update-searchableattributes) so that a field is:
* Searchable but not displayed
* Displayed but not searchable
* Neither displayed nor searchable
In the latter case, the field will be completely ignored during search. However, it will still be [stored](/docs/capabilities/full_text_search/how_to/configure_displayed_attributes#data-storing) in the document.
The `fieldDistribution` object returned by the [`/stats` route](/docs/reference/api/stats) is not affected by `searchableAttributes` or `displayedAttributes`. Even if a field is neither displayed nor searchable, it still appears in `fieldDistribution` with its document count. Rely on `displayedAttributes` and permissions, not on stats output, to keep a field name hidden from clients.
To learn more, refer to our [displayed and searchable attributes guide](/docs/capabilities/full_text_search/how_to/configure_displayed_attributes).
## Primary field
The primary field is a special field that must be present in all documents. Its attribute is the [primary key](/docs/resources/internals/primary_key#primary-field) and its value is the [document id](/docs/resources/internals/primary_key#document-id). If you try to [index a document](/docs/resources/internals/primary_key#primary-field) that's missing a primary key or possessing the wrong primary key for a given index, it will cause an error and no documents will be added.
To learn more, refer to the [primary key explanation](/docs/resources/internals/primary_key).
## Upload
By default, Meilisearch limits the size of all payloads (and therefore document uploads) to 100MB. You can [change the payload size limit](/docs/resources/self_hosting/configuration/reference#payload-limit-size) at runtime using the `http-payload-size-limit` option.
Meilisearch uses a lot of RAM when indexing documents. Be aware of your [RAM availability](/docs/resources/help/faq#what-are-the-recommended-requirements-for-hosting-a-meilisearch-instance) as you increase your batch size as this could cause Meilisearch to crash.
When using the [add new documents endpoint](/docs/reference/api/documents/add-or-update-documents), ensure:
* The payload format is correct. There are no extraneous commas, mismatched brackets, missing quotes, etc.
* All documents are sent in an array, even if there is only one document
### Dataset format
Meilisearch accepts datasets in the following formats:
* [JSON](#json)
* [NDJSON](#ndjson)
* [CSV](#csv)
#### JSON
Documents represented as JSON objects are key-value pairs enclosed by curly brackets. As such, [any rule that applies to formatting JSON objects](https://www.w3schools.com/js/js_json_objects.asp) also applies to formatting Meilisearch documents. For example, an attribute must be a string, while a value must be a valid [JSON data type](https://www.w3schools.com/js/js_json_datatypes.asp).
Meilisearch will only accept JSON documents when it receives the `application/json` content-type header.
As an example, let's say you are creating an index that contains information about movies. A sample document might look like this:
```json theme={null}
{
"id": 1564,
"title": "Kung Fu Panda",
"genres": "Children's Animation",
"release-year": 2008,
"cast": [
{ "Jack Black": "Po" },
{ "Jackie Chan": "Monkey" }
]
}
```
In the above example:
* `"id"`, `"title"`, `"genres"`, `"release-year"`, and `"cast"` are attributes
* Each attribute is associated with a value, for example, `"Kung Fu Panda"` is the value of `"title"`
* The document contains a field with the primary key attribute and a unique document id as its value: `"id": "1564"`
#### NDJSON
NDJSON or jsonlines objects consist of individual lines where each individual line is valid JSON text and each line is delimited with a newline character. Any [rules that apply to formatting NDJSON](https://github.com/ndjson/ndjson-spec) also apply to Meilisearch documents.
Meilisearch will only accept NDJSON documents when it receives the `application/x-ndjson` content-type header.
Compared to JSON, NDJSON has better writing performance and is less CPU and memory intensive. It is easier to validate and, unlike CSV, can handle nested structures.
The above JSON document would look like this in NDJSON:
```json theme={null}
{ "id": 1564, "title": "Kung Fu Panda", "genres": "Children's Animation", "release-year": 2008, "cast": [{ "Jack Black": "Po" }, { "Jackie Chan": "Monkey" }] }
```
#### CSV
CSV files express data as a sequence of values separated by a delimiter character. Meilisearch accepts `string`, `boolean`, and `number` data types for CSV documents. If you don't specify the data type for an attribute, it will default to `string`. Empty fields such as `,,` and `, ,` will be considered `null`.
By default, Meilisearch uses a single comma (`,`) as the delimiter. Use the `csvDelimiter` query parameter with the [add or update documents](/docs/reference/api/documents/add-or-update-documents) or [add or replace documents](/docs/reference/api/documents/add-or-replace-documents) endpoints to set a different character. Any [rules that apply to formatting CSV](https://datatracker.ietf.org/doc/html/rfc4180) also apply to Meilisearch documents.
Meilisearch will only accept CSV documents when it receives the `text/csv` content-type header.
Compared to JSON, CSV has better writing performance and is less CPU and memory intensive.
The above JSON document would look like this in CSV:
```csv theme={null}
"id:number","title:string","genres:string","release-year:number"
"1564","Kung Fu Panda","Children's Animation","2008"
```
Since CSV does not support arrays or nested objects, `cast` cannot be converted to CSV.
### Auto-batching
Auto-batching combines similar operations in the same index into a single batch, then processes them together. This significantly speeds up the indexing process.
Tasks within the same batch share the same values for `startedAt`, `finishedAt`, and `duration`.
If a task fails due to an invalid document, it will be removed from the batch. The rest of the batch will still process normally. If an [`internal`](/docs/reference/errors/overview#errors) error occurs, the whole batch will fail and all tasks within it will share the same `error` object.
#### Auto-batching and task cancellation
If the task you're canceling is part of a batch, Meilisearch interrupts the whole process, discards all progress, and cancels that task. Then, it automatically creates a new batch without the canceled task and immediately starts processing it.
# Vector storage: DiskANN
Source: https://www.meilisearch.com/docs/resources/internals/hannoy
How Meilisearch stores and searches vector embeddings using a disk-backed DiskANN approach, implemented through its Hannoy library for fast and scalable vector search.
Meilisearch stores and searches vector embeddings using a **disk-backed approximate nearest neighbor (ANN)** approach inspired by [DiskANN](https://github.com/microsoft/DiskANN), Microsoft's research on graph-based vector search that scales beyond RAM. The implementation lives in **Hannoy**, Meilisearch's purpose-built vector store library introduced in v1.29.
## What is DiskANN?
DiskANN is a family of techniques for building graph-based vector indexes that live on disk rather than entirely in memory. Traditional vector search engines (like FAISS or HNSWlib) require the full index to fit in RAM, which becomes expensive or impossible at scale. DiskANN's key insight is that careful disk layout, combined with graph-based navigation, can achieve near in-memory search speeds while storing data on SSD.
Meilisearch builds on this approach by combining **HNSW** (Hierarchical Navigable Small World) graph navigation with **LMDB** persistence, giving you fast vector search without RAM limitations.
## Hannoy: Meilisearch's DiskANN implementation
Hannoy implements the core DiskANN principles with HNSW as the graph structure and LMDB as the storage backend. It replaced [Arroy](https://github.com/meilisearch/arroy), Meilisearch's previous vector store based on hyperplane trees (k-d trees).
### Why Hannoy replaced Arroy
While Arroy worked well for lower dimensions, it had limitations:
| Issue | Impact |
| ------------------------- | --------------------------------------------------------- |
| **Dimension sensitivity** | Performance degraded significantly beyond \~20 dimensions |
| **Leaf node comparisons** | Required comparing against all vectors in leaf nodes |
| **Read-heavy indexing** | Over 90% of indexing time spent on I/O and page faults |
| **Complex maintenance** | Tree balancing and temporary file management |
Hannoy addresses all these issues using a graph-based approach.
### Performance improvements
Benchmarks on 1 million documents show dramatic improvements:
#### 768-dimensional embeddings
| Metric | Arroy | Hannoy | Improvement |
| ------------------ | --------- | -------- | --------------- |
| **Build time** | 2,387s | 506s | **4.7x faster** |
| **Search latency** | 190ms | 29ms | **6.5x faster** |
| **Disk usage** | 16.19 GiB | 4.03 GiB | **4x smaller** |
#### 1536-dimensional (quantized)
| Metric | Arroy | Hannoy | Improvement |
| ------------------ | -------- | ------- | -------------- |
| **Build time** | 141s | 67s | **2x faster** |
| **Search latency** | 168ms | 13ms | **13x faster** |
| **Disk usage** | 1.86 GiB | 481 MiB | **4x smaller** |
Real-world impact: One customer's indexing time dropped from 2 months to 6 seconds.
## How it works
### HNSW graph navigation
Hannoy organizes vectors into a hierarchical graph structure:
```
Layer 2: A -------- D (sparse, long-range links)
| |
Layer 1: A -- B --- D -- E (medium density)
| | | |
Layer 0: A-B-C-D-E-F-G-H-I-J (all vectors, short links)
```
**Search process:**
1. Enter at the top layer (sparse, enables fast navigation)
2. Greedily navigate toward the query vector
3. Descend to the next layer at each local minimum
4. Return nearest neighbors from the bottom layer
This "small world" property, where any vector can reach any other through few hops, enables efficient search without checking every vector.
### Disk-backed storage with LMDB
Following the DiskANN philosophy, Hannoy stores the graph structure in LMDB rather than in RAM:
* **Disk-backed**: Handle datasets larger than available RAM
* **Memory-mapped**: OS manages caching automatically
* **Concurrent reads**: Multiple search threads simultaneously
* **\~200 bytes overhead** per vector for graph edges
This is the key advantage over in-memory-only solutions. Your dataset size is limited by disk space, not RAM.
### Incremental updates
Unlike tree-based approaches requiring rebalancing, Hannoy handles updates efficiently:
**Insertions:**
* New vectors added to an in-memory temporary index
* When threshold reached, merges with disk index using streaming operations
* Only \~1% of vectors need re-indexing during merge
**Deletions:**
* Uses DiskANN-style deletion policy
* Patches neighboring links to maintain graph connectivity
* No tombstones or graph gaps
### Quantization support
Binary quantization reduces storage and improves speed for high-dimensional embeddings:
| Dimensions | Full precision | Quantized |
| ---------- | -------------- | ------------------ |
| 768 | 3 KiB/vector | \~100 bytes/vector |
| 1536 | 6 KiB/vector | \~200 bytes/vector |
| 3072 | 12 KiB/vector | \~400 bytes/vector |
Quantized vectors use Hamming distance for extremely fast comparisons.
### Distance metrics
| Metric | Use case |
| --------------- | ------------------------------- |
| **Cosine** | Text embeddings (normalized) |
| **Euclidean** | General purpose |
| **Dot product** | When vectors are pre-normalized |
| **Hamming** | Binary/quantized vectors |
## Filtered search
Hannoy integrates with Meilisearch's filtering using RoaringBitmaps:
```json theme={null}
POST /indexes/products/search
{
"vector": [0.123, 0.456, ...],
"filter": "price < 50 AND in_stock = true",
"hybrid": {
"semanticRatio": 1.0,
"embedder": "default"
}
}
```
The filter is evaluated first, then HNSW search operates only on matching documents.
## Multiple embedders
Configure different embedders for different use cases:
```json theme={null}
PATCH /indexes/products/settings
{
"embedders": {
"text": {
"source": "openAi",
"model": "text-embedding-3-small",
"documentTemplate": "{{doc.title}} {{doc.description}}"
},
"image": {
"source": "userProvided",
"dimensions": 512
}
}
}
```
Each embedder maintains its own HNSW graph.
## Configuration
**Memory prefetching**: Reduce cold-start latency by preloading graph structure:
```bash theme={null}
export HANNOY_READER_PREFETCH_MEMORY=true
```
This can reduce initial search latency by several milliseconds.
## Related concepts
* [AI-powered search](/docs/capabilities/hybrid_search/getting_started): Using vector search in Meilisearch
* [Storage](/docs/resources/internals/storage): The LMDB storage backend
## Next steps
* [Hannoy on GitHub](https://github.com/nnethercott/hannoy): Source code
* [From Trees to Graphs: Speeding Up Vector Search 10x](https://blog.kerollmops.com/from-trees-to-graphs-speeding-up-vector-search-10x-with-hannoy): Technical deep-dive
# Indexes
Source: https://www.meilisearch.com/docs/resources/internals/indexes
An index is a collection of documents, much like a table in MySQL or a collection in MongoDB.
An index is a group of documents with associated settings. It is comparable to a table in `SQL` or a collection in MongoDB.
An index is defined by a `uid` and contains the following information:
* One [primary key](#primary-key)
* Customizable [settings](#index-settings)
* An arbitrary number of documents
#### Example
Suppose you manage a database that contains information about movies, similar to [IMDb](https://imdb.com/). You would probably want to keep multiple types of documents, such as movies, TV shows, actors, directors, and more. Each of these categories would be represented by an index in Meilisearch.
Using an index's settings, you can customize search behavior for that index. For example, a `movies` index might contain documents with fields like `movie_id`, `title`, `genre`, `overview`, and `release_date`. Using settings, you could make a movie's `title` have a bigger impact on search results than its `overview`, or make the `movie_id` field non-searchable.
One index's settings do not impact other indexes. For example, you could use a different list of synonyms for your `movies` index than for your `costumes` index, even if they're on the same server.
## Index creation
### Implicit index creation
If you try to add documents or settings to an index that does not already exist, Meilisearch will automatically create it for you.
### Explicit index creation
You can explicitly create an index using the [create index endpoint](/docs/reference/api/indexes/create-index). Once created, you can add documents using the [add documents endpoint](/docs/reference/api/documents/add-or-update-documents).
While implicit index creation is more convenient, requiring only a single API request, **explicit index creation is considered safer for production**. This is because implicit index creation bundles multiple actions into a single task. If one action completes successfully while the other fails, the problem can be difficult to diagnose.
## Index UID
The `uid` is the **unique identifier** of an index. It is set when creating the index and must be an integer or string containing only alphanumeric characters `a-z A-Z 0-9`, hyphens `-` and underscores `_`.
```json theme={null}
{
"uid": "movies",
"createdAt": "2019-11-20T09:40:33.711324Z",
"updatedAt": "2019-11-20T10:16:42.761858Z"
}
```
You can change an index's `uid` using the [`/indexes` API route](/docs/reference/api/indexes/update-index).
## Primary key
Every index has a primary key: a required attribute that must be present in all documents in the index. Each document must have a unique value associated with this attribute.
The primary key serves to identify each document, such that two documents in an index can never be completely identical. If you add two documents with the same value for the primary key, they will be treated as the same document: one will overwrite the other. If you try adding documents, and even a single one is missing the primary key, none of the documents will be stored.
You can set the primary key for an index or let it be inferred by Meilisearch. Read more about [setting the primary key](/docs/resources/internals/primary_key#setting-the-primary-key).
[Learn more about the primary field](/docs/resources/internals/primary_key)
## Index settings
Index settings can be thought of as a JSON object containing many different options for customizing search behavior.
To change index settings, use the [update settings endpoint](/docs/reference/api/settings/update-all-settings) or any of the child routes.
### Displayed and searchable attributes
By default, every document field is searchable and displayed in response to search queries. However, you can choose to set some fields as non-searchable, non-displayed, or both.
You can update these field attributes using the [update settings endpoint](/docs/reference/api/settings/update-all-settings), or the respective endpoints for [displayed attributes](/docs/reference/api/settings/update-displayedattributes) and [searchable attributes](/docs/reference/api/settings/update-searchableattributes).
[Learn more about displayed and searchable attributes.](/docs/capabilities/full_text_search/how_to/configure_displayed_attributes)
### Distinct attribute
If your dataset contains multiple similar documents, you may want to return only one on search. Suppose you have numerous black jackets in different sizes in your `costumes` index. Setting `costume_name` as the distinct attribute will mean Meilisearch will not return more than one black jacket with the same `costume_name`.
Designate the distinct attribute using the [update settings endpoint](/docs/reference/api/settings/update-all-settings) or the [update distinct attribute endpoint](/docs/reference/api/settings/update-distinctattribute). **You can only set one field as the distinct attribute per index.**
[Learn more about distinct attributes.](/docs/capabilities/full_text_search/how_to/configure_distinct_attribute)
### Faceting
Facets are a specific use-case of filters in Meilisearch: whether something is a facet or filter depends on your UI and UX design. Like filters, you need to add your facets to [`filterableAttributes`](/docs/reference/api/settings/update-filterableattributes), then make a search query using the [`filter` search parameter](/docs/reference/api/search/search-with-post#body-filter).
By default, Meilisearch returns `100` facet values for each faceted field. You can change this using the [update settings endpoint](/docs/reference/api/settings/update-all-settings) or the [update faceting settings endpoint](/docs/reference/api/settings/update-facetsearch).
[Learn more about faceting.](/docs/capabilities/filtering_sorting_faceting/how_to/filter_with_facets)
### Filterable attributes
Filtering allows you to refine your search based on different categories. For example, you could search for all movies of a certain `genre`: `Science Fiction`, with a `rating` above `8`.
Before filtering on any document attribute, you must add it to `filterableAttributes` using the [update settings endpoint](/docs/reference/api/settings/update-all-settings) or the [update filterable attributes endpoint](/docs/reference/api/settings/update-filterableattributes). Then, make a search query using the [`filter` search parameter](/docs/reference/api/search/search-with-post#body-filter).
[Learn more about filtering.](/docs/capabilities/filtering_sorting_faceting/getting_started)
### Pagination
To protect your database from malicious scraping, Meilisearch only returns up to `1000` results for a search query. You can change this limit using the [update settings endpoint](/docs/reference/api/settings/update-all-settings) or the [update pagination settings endpoint](/docs/reference/api/settings/update-pagination).
[Learn more about pagination.](/docs/capabilities/full_text_search/how_to/paginate_search_results)
### Ranking rules
Meilisearch uses ranking rules to sort matching documents so that the most relevant documents appear at the top. All indexes are created with the same built-in ranking rules executed in default order. The order of these rules matters: the first rule has the most impact, and the last rule has the least.
You can alter this order or define custom ranking rules to return certain results first. This can be done using the [update settings endpoint](/docs/reference/api/settings/update-all-settings) or the [update ranking rules endpoint](/docs/reference/api/settings/update-rankingrules).
[Learn more about ranking rules.](/docs/capabilities/full_text_search/relevancy/relevancy)
### Sortable attributes
By default, Meilisearch orders results according to their relevancy. You can alter this sorting behavior to show certain results first.
Add the attributes you'd like to sort by to `sortableAttributes` using the [update settings endpoint](/docs/reference/api/settings/update-all-settings) or the [update sortable attributes endpoint](/docs/reference/api/settings/update-sortableattributes). You can then use the [`sort` search parameter](/docs/reference/api/search/search-with-post#body-sort) to sort your results in ascending or descending order.
[Learn more about sorting.](/docs/capabilities/filtering_sorting_faceting/how_to/sort_results)
### Stop words
Your dataset may contain words you want to ignore during search because, for example, they don't add semantic value or occur too frequently (for instance, `the` or `of` in English). You can add these words to the [stop words list](/docs/reference/api/settings/get-stopwords) and Meilisearch will ignore them during search.
Change your index's stop words list using the [update settings endpoint](/docs/reference/api/settings/update-all-settings) or the [update stop words endpoint](/docs/reference/api/settings/update-stopwords). In addition to improving relevancy, designating common words as stop words greatly improves performance.
[Learn more about stop words.](/docs/reference/api/settings/get-stopwords)
### Synonyms
Your dataset may contain words with similar meanings. For these, you can define a list of synonyms: words that will be treated as the same or similar for search purposes. Words set as synonyms won't always return the same results due to factors like typos and splitting the query.
Since synonyms are defined for a given index, they won't apply to any other index on the same Meilisearch instance. You can create your list of synonyms using the [update settings endpoint](/docs/reference/api/settings/update-all-settings) or the [update synonyms endpoint](/docs/reference/api/settings/update-synonyms).
[Learn more about synonyms.](/docs/capabilities/full_text_search/relevancy/synonyms)
### Typo tolerance
Typo tolerance is a built-in feature that helps you find relevant results even when your search queries contain spelling mistakes or typos, for example, typing `chickne` instead of `chicken`. This setting allows you to do the following for your index:
* Enable or disable typo tolerance
* Configure the minimum word size for typos
* Disable typos on specific words
* Disable typos on specific document attributes
You can update the typo tolerance settings using the [update settings endpoint](/docs/reference/api/settings/update-all-settings) or the [update typo tolerance endpoint](/docs/reference/api/settings/update-typotolerance).
[Learn more about typo tolerance.](/docs/capabilities/full_text_search/relevancy/typo_tolerance_settings)
## Swapping indexes
Suppose you have an index in production, `movies`, where your users are currently making search requests. You want to deploy a new version of `movies` with different settings, but updating it normally could cause downtime for your users. This problem can be solved using index swapping.
To use index swapping, you would create a second index, `movies_new`, containing all the changes you want to make to `movies`.
This means that the documents, settings, and task history of `movies` will be swapped with the documents, settings, and task history of `movies_new` **without any downtime for the search clients**. The task history of `enqueued` tasks is not modified.
Once swapped, your users will still be making search requests to the `movies` index but it will contain the data of `movies_new`. You can delete `movies_new` after the swap or keep it in case something goes wrong and you want to swap back.
Swapping indexes is an atomic transaction: **either all indexes are successfully swapped, or none are**.
For more information, see the [swap indexes endpoint](/docs/reference/api/indexes/swap-indexes).
# Prefix search
Source: https://www.meilisearch.com/docs/resources/internals/prefix
Prefix search is a core part of Meilisearch's design and allows users to receive results even when their query only contains a single letter.
In Meilisearch, **you can perform a search with only a single letter as your query**. This is because we follow the philosophy of **prefix search**.
Prefix search is when document sorting starts by comparing the search query against the beginning of each word in your dataset. All documents with words that match the query term are added to the [bucket sort](https://en.wikipedia.org/wiki/Bucket_sort), before the [ranking rules](/docs/capabilities/full_text_search/relevancy/ranking_rules) are applied sequentially.
In other words, prefix search means that it's not necessary to type a word in its entirety to find documents containing that word. You can just type the first one or two letters.
Prefix search is only performed on the last word in a search query; prior words must be typed out fully to get accurate results.
Searching by prefix (rather than using complete words) has a significant impact on search time. The shorter the query term, the more possible matches in the dataset.
### Example
Given a set of words in a dataset:
`film` `cinema` `movies` `show` `harry` `potter` `shine` `musical`
query: `s`:
response:
* `show`
* `shine`
but not
* `movies`
* `musical`
query: `sho`:
response:
* `show`
Meilisearch also handles typos while performing the prefix search. You can [read more about the typo rules on the dedicated page](/docs/capabilities/full_text_search/relevancy/typo_tolerance_settings).
### Disabling prefix search
You can disable prefix search entirely using the [`prefixSearch` index setting](/docs/reference/api/settings/get-prefixsearch). Set it to `disabled` to turn off prefix search for an index. The default value is `indexingTime`, which enables prefix search.
We also [apply splitting and concatenating on search queries](/docs/resources/internals/concat).
# Primary key
Source: https://www.meilisearch.com/docs/resources/internals/primary_key
The primary key is a special field that must be present in all documents indexed by Meilisearch.
## Primary field
An [index](/docs/resources/internals/indexes) in Meilisearch is a collection of [documents](/docs/resources/internals/documents). Documents are composed of fields, each field containing an attribute and a value.
The primary field is a special field that must be present in all documents. Its attribute is the **[primary key](#primary-key-1)** and its value is the **[document id](#document-id)**. It uniquely identifies each document in an index, ensuring that **it is impossible to have two exactly identical documents** present in the same index.
### Example
Suppose we have an index of books. Each document contains a number of fields with data on the book's `author`, `title`, and `price`. More importantly, each document contains a **primary field** consisting of the index's **primary key** `id` and a **unique id**.
```json theme={null}
[
{
"id": 1,
"title": "Diary of a Wimpy Kid: Rodrick Rules",
"author": "Jeff Kinney",
"genres": ["comedy","humor"],
"price": 5.00
},
{
"id": 2,
"title": "Black Leopard, Red Wolf",
"author": "Marlon James",
"genres": ["fantasy","drama"],
"price": 5.00
}
]
```
Aside from the primary key, **documents in the same index are not required to share attributes**. A book in this dataset could be missing the `title` or `genre` attribute and still be successfully indexed by Meilisearch, provided it has the `id` attribute.
### Primary key
The primary key is the attribute of the primary field.
Every index has a primary key, an attribute that must be shared across all documents in that index. If you attempt to add documents to an index and even a single one is missing the primary key, **none of the documents will be stored.**
#### Example
```json theme={null}
{
"id": 1,
"title": "Diary of a Wimpy Kid",
"author": "Jeff Kinney",
"genres": ["comedy","humor"],
"price": 5.00
}
```
Each document in the above index is identified by a primary field containing the primary key `id` and a unique document id value.
### Document id
The document id is the value associated with the primary key. It is part of the primary field and acts as a unique identifier for each document in a given index.
Two documents in an index can have the same values for all attributes except the primary key. If two documents in the same index have the same id, then they are treated as the same document and **the preceding document will be overwritten**.
Document addition requests in Meilisearch are atomic. This means that **if the primary field value of even a single document in a batch is incorrectly formatted, an error will occur, and Meilisearch will not index documents in that batch.**
#### Example
Good:
```json theme={null}
"id": "_Aabc012_"
```
Bad:
```json theme={null}
"id": "@BI+* ^5h2%"
```
#### Formatting the document id
The document id must be an integer or a string. If the id is a string, it can only contain alphanumeric characters (`a-z`, `A-Z`, `0-9`), hyphens (`-`), and underscores (`_`).
## Setting the primary key
You can set the primary key explicitly or let Meilisearch infer it from your dataset. Whatever your choice, an index can have only one primary key at a time, and the primary key cannot be changed while documents are present in the index.
### Setting the primary key on index creation
When creating an index manually, you can explicitly indicate the primary key you want that index to use.
The code below creates an index called `books` and sets `reference_number` as its primary key:
```bash cURL theme={null}
curl \
-X POST 'MEILISEARCH_URL/indexes' \
-H 'Content-Type: application/json' \
--data-binary '{
"uid": "books",
"primaryKey": "reference_number"
}'
```
```javascript JS theme={null}
client.createIndex('books', { primaryKey: 'reference_number' })
```
```python Python theme={null}
client.create_index('books', {'primaryKey': 'reference_number'})
```
```php PHP theme={null}
$client->createIndex('books', ['primaryKey' => 'reference_number']);
```
```java Java theme={null}
client.createIndex("books", "reference_number");
```
```ruby Ruby theme={null}
client.create_index('books', primary_key: 'reference_number')
```
```go Go theme={null}
client.CreateIndex(&meilisearch.IndexConfig{
Uid: "books",
PrimaryKey: "reference_number",
})
```
```csharp C# theme={null}
TaskInfo task = await client.CreateIndexAsync("books", "reference_number");
```
```rust Rust theme={null}
client
.create_index("books", Some("reference_number"))
.await
.unwrap();
```
```swift Swift theme={null}
client.createIndex(uid: "books", primaryKey: "reference_number") { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart theme={null}
await client.createIndex('books', primaryKey: 'reference_number');
```
```json theme={null}
{
"taskUid": 1,
"indexUid": "books",
"status": "enqueued",
"type": "indexCreation",
"enqueuedAt": "2022-09-20T12:06:24.364352Z"
}
```
### Setting the primary key on document addition
When adding documents to an empty index, you can explicitly set the index's primary key as part of the document addition request.
The code below adds a document to the `books` index and sets `reference_number` as that index's primary key:
```bash cURL theme={null}
curl \
-X POST 'MEILISEARCH_URL/indexes/books/documents?primaryKey=reference_number' \
-H 'Content-Type: application/json' \
--data-binary '[
{
"reference_number": 287947,
"title": "Diary of a Wimpy Kid",
"author": "Jeff Kinney",
"genres": [
"comedy",
"humor"
],
"price": 5.00
}
]'
```
```javascript JS theme={null}
client.index('books').addDocuments([
{
reference_number: 287947,
title: 'Diary of a Wimpy Kid',
author: 'Jeff Kinney',
genres: ['comedy','humor'],
price: 5.00
}
], { primaryKey: 'reference_number' })
```
```python Python theme={null}
client.index('books').add_documents([{
'reference_number': 287947,
'title': 'Diary of a Wimpy Kid',
'author': 'Jeff Kinney',
'genres': ['comedy', 'humor'],
'price': 5.00
}], 'reference_number')
```
```php PHP theme={null}
$client->index('books')->addDocuments([
[
'reference_number' => 287947,
'title' => 'Diary of a Wimpy Kid',
'author' => 'Jeff Kinney',
'genres' => ['comedy', 'humor'],
'price' => 5.00
]
], 'reference_number');
```
```java Java theme={null}
client.index("books").addDocuments("[{"
+ "\"reference_number\": 2879,"
+ "\"title\": \"Diary of a Wimpy Kid\","
+ "\"author\": \"Jeff Kinney\","
+ "\"genres\": [\"comedy\", \"humor\"],"
+ "\"price\": 5.00"
+ "}]"
, "reference_number");
```
```ruby Ruby theme={null}
client.index('books').add_documents([
{
reference_number: 287947,
title: 'Diary of a Wimpy Kid',
author: 'Jeff Kinney',
genres: ['comedy', 'humor'],
price: 5.00
}
], 'reference_number')
```
```go Go theme={null}
documents := []map[string]interface{}{
{
"reference_number": 287947,
"title": "Diary of a Wimpy Kid",
"author": "Jeff Kinney",
"genres": []string{"comedy", "humor"},
"price": 5.00,
},
}
refrenceNumber := "reference_number"
client.Index("books").AddDocuments(documents, &refrenceNumber)
```
```csharp C# theme={null}
await index.AddDocumentsAsync(
new[] {
new Book {
ReferenceNumber = 287947,
Title = "Diary of a Wimpy Kid",
Author = "Jeff Kinney",
Genres = new string[] { "comedy", "humor" },
Price = 5.00
}
},
"reference_number");
```
```rust Rust theme={null}
#[derive(Serialize, Deserialize)]
struct Book {
reference_number: String,
title: String,
author: String,
genres: Vec,
price: f64
}
let task: TaskInfo = client
.index("books")
.add_documents(&[
Book {
reference_number: "287947".to_string(),
title: "Diary of a Wimpy Kid".to_string(),
author: "Jeff Kinney".to_string(),
genres: vec!["comedy".to_string(),"humor".to_string()],
price: 5.00
}
], Some("reference_number"))
.await
.unwrap();
```
```swift Swift theme={null}
let documents: Data = """
[
{
"reference_number": 287947,
"title": "Diary of a Wimpy Kid",
"author": "Jeff Kinney",
"genres": ["comedy", "humor"],
"price": 5
}
]
""".data(using: .utf8)!
client.index("books").addDocuments(documents: documents, primaryKey: "reference_number") { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart theme={null}
await client.index('movies').addDocuments([
{
'reference_number': 287947,
'title': 'Diary of a Wimpy Kid',
'author': 'Jeff Kinney',
'genres': ['comedy', 'humor'],
'price': 5.00
}
], primaryKey: 'reference_number');
```
**Response:**
```json theme={null}
{
"taskUid": 1,
"indexUid": "books",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "2022-09-20T12:08:55.463926Z"
}
```
### Changing your primary key with the update index endpoint
The primary key cannot be changed while documents are present in the index. To change the primary key of an index that already contains documents, you must therefore [delete all documents](/docs/reference/api/documents/delete-all-documents) from that index, [change the primary key](/docs/reference/api/indexes/update-index), then [add them](/docs/reference/api/documents/add-or-replace-documents) again.
The code below updates the primary key to `title`:
```bash cURL theme={null}
curl \
-X PATCH 'MEILISEARCH_URL/indexes/books' \
-H 'Content-Type: application/json' \
--data-binary '{ "primaryKey": "title" }'
```
```javascript JS theme={null}
client.updateIndex('books', {
primaryKey: 'title'
})
```
```python Python theme={null}
client.index('books').update(primary_key='title')
```
```php PHP theme={null}
$client->updateIndex('books', ['primaryKey' => 'title']);
```
```java Java theme={null}
client.updateIndex("books", "title");
```
```ruby Ruby theme={null}
client.index('books').update(primary_key: 'title')
```
```go Go theme={null}
client.Index("books").UpdateIndex(&meilisearch.UpdateIndexRequestParams{
PrimaryKey: "title",
})
```
```csharp C# theme={null}
TaskInfo task = await client.UpdateIndexAsync("books", "title");
```
```rust Rust theme={null}
let task = IndexUpdater::new("books", &client)
.with_primary_key("title")
.execute()
.await
.unwrap();
```
```swift Swift theme={null}
client.updateIndex(uid: "movies", primaryKey: "title") { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart theme={null}
await client.updateIndex('books', 'title');
```
**Response:**
```json theme={null}
{
"taskUid": 1,
"indexUid": "books",
"status": "enqueued",
"type": "indexUpdate",
"enqueuedAt": "2022-09-20T12:10:06.444672Z"
}
```
### Meilisearch guesses your primary key
Suppose you add documents to an index without previously setting its primary key. In this case, Meilisearch will automatically look for an attribute ending with the string `id` in a case-insensitive manner (for example, `uid`, `BookId`, `ID`) in your first document and set it as the index's primary key.
If Meilisearch finds [multiple attributes ending with `id`](#index_primary_key_multiple_candidates_found) or [cannot find a suitable attribute](#index_primary_key_no_candidate_found), it will throw an error. In both cases, the document addition process will be interrupted and no documents will be added to your index.
## Primary key errors
This section covers some primary key errors and how to resolve them.
### `index_primary_key_multiple_candidates_found`
This error occurs when you add documents to an index for the first time and Meilisearch finds multiple attributes ending with `id`. It can be resolved by [manually setting the index's primary key](#setting-the-primary-key-on-document-addition).
```json theme={null}
{
"uid": 4,
"indexUid": "books",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 5,
"indexedDocuments": 5
},
"error": {
"message": "The primary key inference failed as the engine found 2 fields ending with `id` in their names: 'id' and 'author_id'. Please specify the primary key manually using the `primaryKey` query parameter.",
"code": "index_primary_key_multiple_candidates_found",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#index-primary-key-multiple-candidates-found"
},
"duration": "PT0.006002S",
"enqueuedAt": "2023-01-17T10:44:42.625574Z",
"startedAt": "2023-01-17T10:44:42.626041Z",
"finishedAt": "2023-01-17T10:44:42.632043Z"
}
```
### `index_primary_key_no_candidate_found`
This error occurs when you add documents to an index for the first time and none of them have an attribute ending with `id`. It can be resolved by [manually setting the index's primary key](#setting-the-primary-key-on-document-addition), or ensuring that all documents you add possess an `id` attribute.
```json theme={null}
{
"uid": 1,
"indexUid": "books",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 5,
"indexedDocuments": null
},
"error": {
"message": "The primary key inference failed as the engine did not find any field ending with `id` in its name. Please specify the primary key manually using the `primaryKey` query parameter.",
"code": "index_primary_key_no_candidate_found",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#index-primary-key-no-candidate-found"
},
"duration": "PT0.006579S",
"enqueuedAt": "2023-01-17T10:19:14.464858Z",
"startedAt": "2023-01-17T10:19:14.465369Z",
"finishedAt": "2023-01-17T10:19:14.471948Z"
}
```
### `invalid_document_id`
This happens when your document id does not have the correct [format](#formatting-the-document-id). The document id can only be of type integer or string, composed of alphanumeric characters `a-z A-Z 0-9`, hyphens `-`, and underscores `_`.
```json theme={null}
{
"uid": 1,
"indexUid": "books",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 5,
"indexedDocuments": null
},
"error": {
"message": "Document identifier `1@` is invalid. A document identifier can be of type integer or string, only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and underscores (_).",
"code": "invalid_document_id",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#invalid_document_id"
},
"duration": "PT0.009738S",
"enqueuedAt": "2021-12-30T11:28:59.075065Z",
"startedAt": "2021-12-30T11:28:59.076144Z",
"finishedAt": "2021-12-30T11:28:59.084803Z"
}
```
### `missing_document_id`
This error occurs when your index already has a primary key, but one of the documents you are trying to add is missing this attribute.
```json theme={null}
{
"uid": 1,
"indexUid": "books",
"status": "failed",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 1,
"indexedDocuments": null
},
"error": {
"message": "Document doesn't have a `id` attribute: `{\"title\":\"Solaris\",\"author\":\"Stanislaw Lem\",\"genres\":[\"science fiction\"],\"price\":5.0.",
"code": "missing_document_id",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#missing_document_id"
},
"duration": "PT0.007899S",
"enqueuedAt": "2021-12-30T11:23:52.304689Z",
"startedAt": "2021-12-30T11:23:52.307632Z",
"finishedAt": "2021-12-30T11:23:52.312588Z"
}
```
# Meilisearch ranking: a multi-criteria system beyond BM25
Source: https://www.meilisearch.com/docs/resources/internals/ranking
How Meilisearch's multi-criteria ranking system works, why it produces better results than BM25 for user-facing search, and the technical trade-offs involved.
Most search engines rank results using a single relevancy score computed from a formula like BM25 or term frequency. Meilisearch takes a fundamentally different approach: it evaluates multiple ranking criteria sequentially, giving you transparent control over what matters most and delivering results that feel right to end users.
## How traditional search ranking works
### Single-score ranking models
Traditional search engines compute a single numeric score per document, then sort by that number. The two most common approaches are:
**BM25** (Elasticsearch, OpenSearch, Lucene, MongoDB Atlas Search) is the industry standard for full-text search. It scores documents based on:
* **Term frequency (TF)**: How often the query term appears in the document
* **Inverse document frequency (IDF)**: How rare the term is across all documents
* **Document length normalization**: Shorter documents get a slight boost
**PostgreSQL `ts_rank`** is a simpler model used by PostgreSQL full-text search (and Supabase). It uses term frequency with optional document length normalization, but does **not** consider inverse document frequency. PostgreSQL also offers `ts_rank_cd` (cover density), which factors in proximity of matched terms. Both are less sophisticated than BM25.
In all cases, the engine produces a score like `8.72` or `3.14`, and results are sorted by this number in descending order.
### The problem with single-score ranking
BM25 was designed for **information retrieval**, finding research papers, legal documents, or web pages where term frequency genuinely signals relevance. But for **application search** (e-commerce, media catalogs, SaaS dashboards), this model breaks down:
* **Typos are invisible**: BM25 treats "iPhone" and "iPhoone" as completely different terms. The misspelled query returns zero results
* **Word order is ignored**: Searching "dark knight" and "knight dark" produce identical scores, even though user intent clearly favors the first ordering
* **Field importance is flattened**: A match in a product title should matter more than a match in a review comment, but BM25 requires manual field boosting that's fragile and hard to tune
* **Scoring is opaque**: A score of `8.72` means nothing to a developer debugging why result A appears before result B
* **Prefix matching requires workarounds**: A user typing "prog" expects to see "programming", but BM25 doesn't do this without additional analyzers
## How Meilisearch ranks results
Meilisearch replaces the single-score model with a **multi-criteria [bucket sort](/docs/resources/internals/bucket_sort)** system. Instead of computing one number, it evaluates documents through a sequence of ranking rules, each acting as a successive filter.
### The ranking pipeline
When a user searches for `"badman dark knight returns"`, Meilisearch applies ranking rules in order:
```
All matching documents
│
├─ 1. words ──────────── How many query words match?
│ ├─ 4/4 words → Bucket A
│ ├─ 3/4 words → Bucket B
│ └─ 1/4 words → Bucket C
│
├─ 2. typo ───────────── How many typos were needed?
│ ├─ 0 typos → Sub-bucket A.1
│ └─ 1 typo (badman→batman) → Sub-bucket A.2
│
├─ 3. proximity ──────── How close are matched words?
│ ├─ Adjacent → Sub-bucket A.1.1
│ └─ 3 words apart → Sub-bucket A.1.2
│
├─ 4. attributeRank ──── Which field matched?
│ ├─ Title → higher
│ └─ Overview → lower
│
├─ 5. sort ───────────── User-defined sort (if any)
│
├─ 6. wordPosition ───── Where in the field did it match?
│ ├─ Start of field → higher
│ └─ End of field → lower
│
└─ 7. exactness ──────── Exact match or partial?
├─ Exact → higher
└─ Prefix/partial → lower
```
Each rule only operates on documents that **tied** in all previous rules. This means:
* A document matching 4/4 words with 2 typos **always** ranks above a document matching 3/4 words with 0 typos
* The `words` rule has absolute priority over `typo`, which has absolute priority over `proximity`, and so on
* There is no way for a high score in one dimension to compensate for a low score in another
This is called **lexicographic ordering**, the same logic humans use to sort words in a dictionary, applied to search ranking.
### The seven default ranking rules
| Order | Rule | What it measures | Why it matters |
| :---- | :---------------- | :------------------------------------- | :--------------------------------------------------------------- |
| 1 | **words** | Number of query terms matched | Documents matching more of what the user typed are more relevant |
| 2 | **typo** | Number of typos corrected | Exact matches are preferred, but typos still return results |
| 3 | **proximity** | Distance between matched terms | "dark knight" in sequence beats "dark ... knight" far apart |
| 4 | **attributeRank** | Which attribute matched | A title match is more important than a description match |
| 5 | **sort** | User-defined sort order | Only active when the query includes a `sort` parameter |
| 6 | **wordPosition** | Position of match within the attribute | Matching at the start of a title beats matching at the end |
| 7 | **exactness** | Exact match vs prefix/typo match | "knight" exactly beats "knights" (prefix) |
You can [reorder, add, or remove](/docs/capabilities/full_text_search/relevancy/ranking_rules) any of these rules. You can also add [custom ranking rules](/docs/capabilities/full_text_search/relevancy/custom_ranking_rules) that incorporate business logic, like boosting newer products or higher-rated items.
## Why this is better for application search
### 1. Typo tolerance is built in
BM25 either matches a term or it doesn't. Meilisearch's `typo` rule is a first-class ranking criterion: documents matching with fewer typos rank higher than those requiring more corrections, but all of them appear in results.
| Query | BM25 | Meilisearch |
| :--------- | :--------------------- | :------------------------------- |
| `"iphone"` | Returns iPhone results | Returns iPhone results |
| `"iphoen"` | **0 results** | Returns iPhone results (1 typo) |
| `"ipohne"` | **0 results** | Returns iPhone results (2 typos) |
### 2. Word order and proximity matter
BM25 treats a document as a bag of words: the position and distance between terms don't affect the score. Meilisearch's `proximity` rule ensures that documents where query terms appear close together and in order rank higher.
| Query: `"new york pizza"` | BM25 | Meilisearch |
| :---------------------------------------------------- | :------------------ | :--------------------------------- |
| "Best **New York pizza** places" | Same score as below | Ranks **1st** (adjacent, in order) |
| "**New** restaurant in **York** with great **pizza**" | Same score as above | Ranks **2nd** (words spread apart) |
### 3. Field importance is explicit and predictable
With BM25, field boosting requires numeric weights (`title^3 description^1`) that interact unpredictably with term frequency and document length. In Meilisearch, the `attributeRank` rule uses the order of [`searchableAttributes`](/docs/reference/api/settings/update-searchableattributes): the first field always wins over the second, no math involved.
```json theme={null}
// searchableAttributes setting
["title", "brand", "description"]
```
A match in `title` **always** outranks a match in `description` (assuming previous rules tied). No weights to tune, no interactions to debug.
### 4. Ranking is transparent and debuggable
BM25 produces opaque scores. Meilisearch lets you inspect exactly why a document ranks where it does using [`showRankingScoreDetails`](/docs/capabilities/full_text_search/relevancy/ranking_score):
```json theme={null}
{
"title": "Batman: The Dark Knight",
"_rankingScoreDetails": {
"words": { "order": 0, "matchingWords": 3, "maxMatchingWords": 3 },
"typo": { "order": 1, "typoCount": 0, "maxTypoCount": 3 },
"proximity": { "order": 2, "score": 0.98 },
"attributeRank": { "order": 3, "score": 1.0, "attributeRankingOrderScore": 1.0 },
"exactness": { "order": 6, "score": 0.67 }
}
}
```
You can see that this result matched 3/3 words, with 0 typos, high proximity, in the highest-ranked attribute. If a result appears in the wrong position, you can identify which rule caused it and adjust.
### 5. Prefix search works natively
When a user types `"prog"` in a search bar, they expect to see "programming", "progress", "program". BM25 requires n-gram tokenizers or edge-gram analyzers to achieve this. Meilisearch handles it automatically: the last word in a query is always treated as a prefix.
### 6. No per-query tuning required
BM25 deployments often require extensive per-query tuning: function scores, field boosts, decay functions, script scores. Meilisearch's ranking rules are configured once at the index level and work consistently across all queries. The same rules that rank "batman" well also rank "comfortable running shoes" well.
## Trade-offs to be aware of
Meilisearch's approach is optimized for application and site search. There are scenarios where BM25 may be more appropriate:
| Scenario | BM25 | Meilisearch |
| :-------------------------- | :--------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------- |
| **Log analytics** | Better (term frequency matters for finding error patterns) | Not designed for this use case |
| **Academic paper search** | Better (TF-IDF identifies topically relevant papers) | Optimized for short, user-facing queries |
| **Documents > 10KB** | Handles naturally | Best with documents [split into smaller chunks](/docs/capabilities/full_text_search/relevancy/relevancy#chunking-large-documents) |
| **Custom scoring formulas** | Fully customizable via script scores | Fixed rule set with configurable order |
| **Billions of documents** | Horizontally scalable | Designed for millions of documents per index |
## Combining ranking with semantic search
Meilisearch's ranking system works alongside [hybrid search](/docs/capabilities/hybrid_search/getting_started). When you enable an [embedder](/docs/capabilities/hybrid_search/getting_started), Meilisearch combines keyword-based ranking (the rules above) with vector similarity in a single query:
```json theme={null}
{
"q": "comfortable running shoes",
"hybrid": {
"semanticRatio": 0.5,
"embedder": "default"
}
}
```
The `semanticRatio` controls the blend: `0.0` uses only the multi-criteria ranking rules, `1.0` uses only vector similarity, and values in between merge both result sets. This gives you the best of both worlds, BM25-beating keyword relevancy plus semantic understanding, without managing two separate search systems.
## Summary
| | BM25 / ts\_rank (Elasticsearch, PostgreSQL, etc.) | Meilisearch multi-criteria ranking |
| :------------------- | :---------------------------------------------------------- | :----------------------------------------------- |
| **Approach** | Single numeric score per document | Sequential bucket sort through multiple rules |
| **Typo handling** | None (or via fuzzy query, separate step) | Built-in, ranked by typo count |
| **Word proximity** | Not a factor in BM25; basic in PostgreSQL `ts_rank_cd` | Dedicated ranking rule |
| **Field importance** | Numeric boosts with complex interactions | Ordered list, first field always wins |
| **Prefix search** | Requires analyzer config (BM25) or `:*` syntax (PostgreSQL) | Automatic on last query word |
| **Debuggability** | Opaque score | Per-rule breakdown via `showRankingScoreDetails` |
| **Configuration** | Per-query function scores and boosts | Per-index rules, consistent across queries |
| **Semantic search** | Separate system (kNN, vector DB, pgvector) | Integrated via `hybrid` parameter |
| **Best for** | Log analytics, research, large corpora | Application search, e-commerce, media catalogs |
## Learn more
* [Ranking rules](/docs/capabilities/full_text_search/relevancy/ranking_rules): Configure and reorder the seven built-in rules
* [Bucket sort](/docs/resources/internals/bucket_sort): How the bucket sort algorithm works
* [Ranking score](/docs/capabilities/full_text_search/relevancy/ranking_score): Understanding the 0.0–1.0 ranking score
* [Custom ranking rules](/docs/capabilities/full_text_search/relevancy/custom_ranking_rules): Add business logic to ranking
* [Ordering ranking rules](/docs/capabilities/full_text_search/relevancy/ranking_rules#ordering-ranking-rules): Best practices for rule ordering
# Storage
Source: https://www.meilisearch.com/docs/resources/internals/storage
Learn about how Meilisearch stores and handles data in its LMDB storage engine.
Meilisearch is in many ways a database: it stores indexed documents along with the data needed to return relevant search results.
## Database location
Meilisearch creates the database the moment you first launch an instance. By default, you can find it inside a `data.ms` folder located in the same directory as the `meilisearch` binary.
The database location can change depending on a number of factors, such as whether you have configured a different database path with the [`--db-path` instance option](/docs/resources/self_hosting/configuration/reference#database-path), or if you're using an OS virtualization tool like [Docker](https://docker.com).
## LMDB
Creating a database from scratch and managing it is hard work. It would make no sense to try and reinvent the wheel, so Meilisearch uses a storage engine under the hood. This allows the Meilisearch team to focus on improving search relevancy and search performance while abstracting away the complicated task of creating, reading, and updating documents on disk and in memory.
Our storage engine is called [Lightning Memory-Mapped Database](http://www.lmdb.tech/doc/) (LMDB for short). LMDB is a transactional key-value store written in C that was developed for OpenLDAP and has ACID properties. Though we considered other options, such as [Sled](https://github.com/spacejam/sled) and [RocksDB](https://rocksdb.org/), we chose LMDB because it provided us with the best combination of performance, stability, and features.
### Memory mapping
LMDB stores its data in a [memory-mapped file](https://en.wikipedia.org/wiki/Memory-mapped_file). All data fetched from LMDB is returned straight from the memory map, which means there is no memory allocation or memory copy during data fetches.
All documents stored on disk are automatically loaded in memory when Meilisearch asks for them. This ensures LMDB will always make the best use of the RAM available to retrieve the documents.
For best performance, Meilisearch works optimally when the full dataset fits in RAM. In practice, however, we consistently observe that a **RAM‑to‑disk ratio around 1/3 does not materially impact performance**, and for many workloads even \~1/10 works well. The effective memory requirement is highly use‑case‑dependent and varies with search and indexing pressure. RAM can be increased later to unlock more performance, but Meilisearch will not crash simply because the dataset size on disk exceeds the available RAM.
Disk latency is also important for performance: using a **low‑latency disk** (for example, an NVMe SSD) will give better results than a **high‑latency disk** (for example, HDD, NFS, or other network‑mounted storage).
### Understanding LMDB
The choice of LMDB comes with certain pros and cons, especially regarding database size and memory usage. We summarize the most important aspects of LMDB here, but check out this [blog post by LMDB's developers](https://www.symas.com/post/understanding-lmdb-database-file-sizes-and-memory-utilization) for more in-depth information.
#### Database size
When deleting documents from a Meilisearch index, you may notice disk space usage remains the same. This happens because LMDB internally marks that space as free, but does not make it available for the operating system at large. This design choice leads to better performance, as there is no need for periodic compaction operations. As a result, disk space occupied by LMDB (and thus by Meilisearch) tends to increase over time. It is not possible to calculate the precise maximum amount of space a Meilisearch instance can occupy.
#### Memory usage
Since LMDB is memory mapped, it is the operating system that manages the real memory allocated (or not) to Meilisearch.
Thus, if you run Meilisearch as a standalone program on a server, LMDB will use the maximum RAM it can use. More RAM means more of the dataset stays in cache and fewer reads hit disk, but a [RAM‑to‑disk ratio of around 1/3 does not materially impact performance](#memory-mapping) for most workloads.
On the other hand, if you run Meilisearch along with other programs, the OS will manage memory based on everyone's needs. This makes Meilisearch's memory usage quite flexible when used in development.
**Virtual Memory != Real Memory**
Virtual memory is the disk space a program requests from the OS. It is not the memory that the program will actually use.
Meilisearch will always demand a certain amount of space to use as a [memory map](#memory-mapping). This space will be used as virtual memory, but the amount of real memory (RAM) used will be much smaller.
## Measured disk usage
The following measurements were taken using movies.json an 8.6 MB JSON dataset containing 19,553 documents.
After indexing, the dataset size in LMDB is about 122MB.
| Raw JSON | Meilisearch database size on disk | RAM usage | Virtual memory usage |
| :------- | :-------------------------------- | :-------- | :------------------- |
| 9.1 MB | 224 MB | ≃ 305 MB | 205 Gb (memory map) |
This means the database is using **305 MB of RAM and 224 MB of disk space.** Note that [virtual memory](https://www.enterprisestorageforum.com/hardware/virtual-memory/) **refers only to disk space allocated by your computer for Meilisearch; it does not mean that it's actually in use by the database.** See [Memory Usage](#memory-usage) for more details.
These metrics are highly dependent on the machine that is running Meilisearch. Running this test on significantly underpowered machines is likely to give different results.
It is important to note that **there is no reliable way to predict the final size of a database**. This is true for just about any search engine on the market. Meilisearch is no exception.
Database size is affected by a large number of criteria, including settings, relevancy rules, use of facets, the number of different languages present, and more.
# Typo tolerance vs fuzzy search: how Meilisearch handles misspellings
Source: https://www.meilisearch.com/docs/resources/internals/typo_tolerance
How Meilisearch's typo tolerance works under the hood, why it differs from fuzzy search in Elasticsearch, Solr, MongoDB Atlas Search, Manticore, and PostgreSQL, and what the practical implications are.
Most search engines treat typo handling as an optional, query-level feature you opt into. Meilisearch treats it as a first-class ranking criterion that works automatically on every query. This page explains the technical differences and why they matter.
## How Meilisearch handles typos
Meilisearch stores all indexed terms in a single **Finite State Transducer (FST)** built at index time. At query time, the engine generates a [Levenshtein automaton](https://en.wikipedia.org/wiki/Levenshtein_distance) from your search term and intersects it with the pre-built FST in a single streaming pass. This finds all indexed terms within the allowed edit distance efficiently, without scanning the entire dictionary. Meilisearch uses **Damerau-Levenshtein distance**, meaning transpositions (swapped adjacent characters, like `"teh"` → `"the"`) count as a single edit, not two.
Typo tolerance is **on by default** for every index and every query. No query-level parameters are required.
### Word length thresholds
Meilisearch does not apply typo tolerance uniformly. The number of typos allowed depends on the length of the query word:
| Query word length | Typos allowed |
| ----------------- | --------------------- |
| 1–4 characters | 0 (prefix match only) |
| 5–8 characters | 1 |
| 9+ characters | 2 |
The hard cap is **2 typos per word**, regardless of length. Words with 3 or more differences will never match. These thresholds are [configurable](/docs/capabilities/full_text_search/relevancy/typo_tolerance_settings) via `minWordSizeForTypos`.
### Two special typo counting rules
**First-character typo costs 2.** A typo on the first character of a word is counted as two typos, not one. This means "caturday" does not match "saturday" (one substitution on position 1, but it costs 2, exceeding the 1-typo budget for 8-char words). This prevents a class of false positives where only the initial character differs.
**Concatenation costs 1 typo.** When two words are separated by a space, Meilisearch also considers them as a single concatenated candidate with 1 typo. For example, searching for `"any way"` will match documents containing `"anyway"`. No other engine in this comparison handles word-split typos this way.
### Typo tolerance is a ranking rule, not a filter
When a query term matches an indexed term via a typo, that result is not discarded or penalized with a separate score modifier. Instead, typo count feeds directly into the `typo` [ranking rule](/docs/resources/internals/ranking), one of the seven criteria in Meilisearch's [bucket sort](/docs/resources/internals/bucket_sort) pipeline.
This means:
* A document matching with 0 typos always ranks above one matching with 1 typo, all else being equal
* A document matching with 1 typo always ranks above one matching with 2 typos
* A result with 0 typos in a less important attribute (body) outranks a result with 2 typos in a more important attribute (title), because `typo` comes before `attribute` in the ranking pipeline
There is no score blending or weighting. The ordering is strict and transparent. Disabling typo tolerance entirely also disables the `typo` ranking rule, since every returned document would have 0 typos by definition.
### Prefix search and typo tolerance work together
Meilisearch applies prefix search and typo tolerance **simultaneously** on the last word of a query. This means a partial, misspelled word still returns results. For example, searching `"iphoe"` (5 characters, 1 typo budget) can match `"iphone"` as a prefixed, typo-corrected term in a single pass.
Elasticsearch can approximate this by combining an `edge_ngram` tokenizer (for prefix expansion at index time) with a `fuzzy` query at search time, but the two mechanisms work on different levels and require careful coordination. In Meilisearch, prefix and typo tolerance are a single unified step with no extra configuration.
You can [disable prefix search](/docs/reference/api/settings/get-prefixsearch) independently from typo tolerance if needed.
### Split and concatenate: handling word boundary mistakes
Beyond character-level edits, Meilisearch handles a class of mistakes that Levenshtein distance cannot catch: wrong word boundaries.
**Concatenation:** when a user types multiple words, Meilisearch also searches their concatenated forms. For a query `"the news paper"`, it additionally tries `"thenews paper"`, `"the newspaper"`, and `"thenewspaper"`. Concatenation is applied to up to 3 consecutive words, and each concatenated candidate counts as 1 typo in the ranking pipeline.
**Splitting:** when a user types a single word, Meilisearch considers frequency-based splits. For `"newspaper"`, it finds that `"news"` and `"paper"` both have meaningful frequency in the index and tries the split candidate. The split is data-driven: it picks the boundary that maximizes the frequency of both halves in the index dictionary, not a fixed linguistic rule. A split into `"new"` + `"spaper"` is rejected because `"spaper"` has no frequency.
Split words must remain adjacent. A document with `"news"` and `"paper"` separated by other words will not match.
Together, these two mechanisms handle the common real-world case where users omit or add spaces within compound words or multi-word phrases. Elasticsearch can handle compound words through custom token filters (like the `word_delimiter_graph` filter or language-specific compound word decomposers), but this requires upfront index configuration per language and does not cover the query-side concatenation case. See [Concatenated and split queries](/docs/resources/internals/concat) for more detail.
### Language-aware tokenization before typo matching
Meilisearch's tokenizer, [Charabia](https://github.com/meilisearch/charabia), normalizes and segments text **before** typo tolerance runs. This matters because typo matching operates on tokens, not raw characters, and what counts as a token depends on the language.
Key transformations that affect typo matching:
| Language / Feature | What Charabia does | Why it matters for typos |
| ------------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| **All Latin scripts** | Lowercase, decompose accents, remove diacritics | `"café"` and `"cafe"` are the same token (no typo budget wasted on accents) |
| **CamelCase** | Splits `"iPhone"` into `"i"` + `"phone"` | Searching `"iphoen"` can match the `"phone"` token with 1 typo |
| **German** | Decomposes compound words (`"Krankenhaus"` → `"kranken"` + `"haus"`) | Each part is independently typo-matchable |
| **Arabic** | Removes the definite article `"ال"` | `"الكتاب"` and `"كتاب"` are treated as the same root |
| **Turkish** | Specialized case folding (dotted/dotless i) | `"I"` and `"ı"` don't incorrectly cost a typo |
| **Chinese / Japanese / Korean** | Dictionary-based segmentation (jieba, lindera) | Words are correctly isolated before character-level matching |
| **Greek** | Final sigma handling | `"λόγος"` and `"λόγοσ"` normalize to the same form |
In contrast, engines like Elasticsearch, Solr, and Manticore apply edit distance after their configured analyzer runs. If the analyzer includes ASCII folding, accents are normalized before matching. But normalization is opt-in and per-field: without explicit configuration, an accent, a case difference, or a language-specific ligature can consume part of the typo budget or cause misses entirely. Charabia applies the right normalization automatically based on the detected language, with no per-field setup required. PostgreSQL `pg_trgm` is always raw: trigrams of `"café"` and `"cafe"` differ regardless of configuration.
### Surgical disable controls
Meilisearch gives you four independent knobs to turn typo tolerance off for specific situations, without affecting the rest:
| Setting | Scope | Use case |
| --------------------- | ------------------------ | --------------------------------------------------------------- |
| `enabled: false` | Entire index | Massive or multilingual datasets where false positives dominate |
| `disableOnWords` | Specific query terms | Brand names, proper nouns, product codes you want exact |
| `disableOnAttributes` | Specific document fields | SKU, barcode, serial number fields where precision matters |
| `disableOnNumbers` | All numeric tokens | Prevents `2024` matching `2025`, improves indexing performance |
Elasticsearch can achieve similar granularity through per-field analyzer configuration and query-level `fuzziness` overrides, but it requires per-query code changes or separate index mappings. Meilisearch exposes all of these as index-level settings applied consistently across every query.
***
## How other engines handle typos
### Elasticsearch and OpenSearch
Elasticsearch (and OpenSearch, which shares the same Lucene core) uses fuzzy queries based on Levenshtein distance, but they must be **explicitly enabled per query** with the `fuzziness` parameter:
```json theme={null}
{
"query": {
"match": {
"title": {
"query": "iphoen",
"fuzziness": "AUTO"
}
}
}
}
```
`fuzziness: "AUTO"` applies similar length-based thresholds, but they differ from Meilisearch's defaults:
| Word length | Elasticsearch AUTO | Meilisearch default |
| ----------- | ------------------ | ------------------- |
| 1-2 chars | 0 edits | 0 typos |
| 3-5 chars | 1 edit | 0 typos |
| 5-8 chars | 2 edits | 1 typo |
| 9+ chars | 2 edits | 2 typos |
Elasticsearch is more permissive for short words (allows 1 edit from 3 characters vs Meilisearch's threshold of 5), which increases recall but also false positives on short terms. However:
* **Opt-in**: if you forget to add `fuzziness` to a query, typos return zero results
* **Score modifier**: fuzzy matches lower the BM25 score, but the score is still a single number mixing term frequency, IDF, and fuzziness penalty into an opaque value
* **Not a ranking rule**: there is no way to say "always prefer 0-typo matches over 1-typo matches regardless of term frequency." A frequent misspelled term can outscore a rare exact match
* **Prefix queries are separate**: `fuzzy` and `prefix` are two distinct query types in Elasticsearch. Combining them requires a `bool` query with both a `fuzzy` clause and a `prefix` clause, or using an `edge_ngram` tokenizer at index time. It is achievable, but requires deliberate setup and adds complexity to every query
**Normalization and custom tokenizers.** Where Elasticsearch has a genuine advantage is in its analyzer system. You can build a fully custom pipeline: any combination of character filters (strip HTML, map characters), tokenizers (standard, whitespace, ngram, edge-ngram, pattern, language-specific), and token filters (lowercase, stemmer, synonym, ASCII folding, stop words, phonetic). This makes Elasticsearch extremely powerful for domain-specific normalization: a medical search engine can apply specialized stemming, a legal platform can expand abbreviations, a multilingual product catalog can use the ICU analyzer with Unicode-aware case folding and decomposition across all scripts. Charabia provides built-in normalization for the most common languages, but Elasticsearch's analyzer framework is more flexible for advanced or unusual requirements. The trade-off is that getting it right requires significant configuration expertise, and misconfigured analyzers are a common source of relevance bugs.
### Apache Solr
Solr is built on the same Lucene engine as Elasticsearch. Fuzzy matching uses the `~` tilde syntax in query strings, or the `fuzzy` query type in JSON:
```
q=title:iphoen~1
```
The `~N` suffix sets the maximum edit distance (0, 1, or 2). Behavior is identical to Elasticsearch at the Lucene level:
* **Opt-in per query**: not automatic
* **Lucene fuzzy query**: edit distance computed at query time, Levenshtein automata generated on the fly
* **BM25 score modifier**: fuzzy matches reduce the document's relevance score; no strict bucket ordering
* **No prefix fuzzy**: the tilde syntax does not combine prefix expansion with fuzzy matching
### MongoDB Atlas Search
MongoDB Atlas Search is built on Lucene and exposes a `fuzzy` option within the `text` operator:
```json theme={null}
{
"$search": {
"text": {
"query": "iphoen",
"path": "title",
"fuzzy": {
"maxEdits": 2,
"prefixLength": 3
}
}
}
}
```
* **Opt-in**: the `fuzzy` option must be added explicitly; standard `text` queries do not tolerate typos
* **`prefixLength`**: the first N characters must match exactly before fuzzy expansion applies, which improves performance but reduces coverage for early-position typos
* **Lucene scoring**: fuzzy matches lower the relevance score, same BM25 mechanics as Elasticsearch and Solr
* **Computed at query time**: automata are generated on the fly per query
### Manticore Search
Manticore Search (a fork of Sphinx) supports fuzzy matching via the `MATCH` function with a `fuzzy` flag or using `levenshtein()` in expressions:
```sql theme={null}
SELECT * FROM movies WHERE MATCH('@title iphoen~2');
```
Or with the HTTP API using the `fuzziness` parameter in a way similar to Elasticsearch (Manticore offers an Elasticsearch-compatible API layer).
* **Opt-in**: fuzzy matching must be explicitly invoked per query
* **Levenshtein distance**: computed at query time
* **Score modifier**: fuzzy matches reduce the BM25-based relevance weight
* **No automatic prefix+fuzzy**: prefix and fuzzy are separate matching modes
### PostgreSQL (`pg_trgm`)
PostgreSQL's `pg_trgm` extension uses **trigram similarity** rather than edit distance. It splits strings into overlapping 3-character substrings and measures how many trigrams two strings share:
```sql theme={null}
SELECT * FROM movies
WHERE similarity(title, 'iphoen') > 0.3
ORDER BY similarity(title, 'iphoen') DESC;
```
This is a fundamentally different approach:
* **Statistical, not edit-based**: "iphone" and "iphoen" share many trigrams (`iph`, `pho`, `hoe`, `oen`) so they score well. But short-word false positives are common because short strings share few trigrams in general
* **Threshold tuning required**: the similarity threshold (default 0.3) must be manually tuned per use case
* **Not automatic**: requires explicit `similarity()` calls or GIN/GIST indexes with the `%` operator
* **No ranking integration**: similarity is a plain score on top of SQL `WHERE` clauses, not a search ranking rule
* **No prefix awareness**: trigram similarity is not prefix-aware. "prog" does not naturally match "programming" via trigrams the way prefix DFA does
***
## Learn more
* [Typo tolerance settings](/docs/capabilities/full_text_search/relevancy/typo_tolerance_settings): configure thresholds, disable on words or numbers, and more
* [Typo tolerance calculations](/docs/capabilities/full_text_search/relevancy/typo_tolerance_settings#how-typo-tolerance-works): how edit distance is computed in detail
* [Concatenated and split queries](/docs/resources/internals/concat): how Meilisearch handles word boundary mistakes
* [Prefix search](/docs/resources/internals/prefix): how prefix matching works and how it interacts with typo tolerance
* [Language support](/docs/resources/help/language): Charabia's tokenization and normalization per language
* [Ranking rules](/docs/capabilities/full_text_search/relevancy/ranking_rules): how the `typo` rule fits into the full ranking pipeline
* [Ranking vs BM25](/docs/resources/internals/ranking): why Meilisearch's multi-criteria system produces better results for application search
# Accessing previous docs versions
Source: https://www.meilisearch.com/docs/resources/migration/previous_docs_version
Meilisearch documentation only covers the engine's latest stable release. Learn how to access the docs for previous Meilisearch versions.
This documentation website only covers the latest stable release of Meilisearch. However, it is possible to view the documentation of previous Meilisearch versions stored in [our GitHub repository](https://github.com/meilisearch/documentation).
This guide shows you how to clone Meilisearch's documentation repository, fetch the content for a specific version, and read it on your local machine.
While this guide's goal is to help users of old versions accomplish their bare minimum needs, it is not intended as a long-term solution or to encourage users to continue using outdated versions of Meilisearch. In almost every case, **it is better to upgrade to the latest Meilisearch version**.
Depending on the version in question, the process of accessing old documentation may be difficult or error-prone. You have been warned!
## Prerequisites
To follow this guide, you should have some familiarity with the command line. Before beginning, make sure the following tools are installed on your machine:
* [Git](https://git-scm.com/)
* [Node v14](https://nodejs.org/en/)
* [Yarn](https://classic.yarnpkg.com/en/)
* [Python 3](https://www.python.org)
## Clone the repository
To access previous versions of the Meilisearch documentation, the first step is downloading the documentation repository into your local machine. In Git, this is referred to as cloning.
Open your console and run the following command. It will create a `documentation` directory in your current location containing the Meilisearch documentation site project files:
```sh theme={null}
git clone https://github.com/meilisearch/documentation.git
```
Alternatively, you may [clone the repository using an SSH URL](https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-ssh-urls).
## Select a Meilisearch version
The documentation repository contains tags for versions from `v0.8` up to the latest release. Use these tags together with `git checkout` to access a specific version.
For example, the following command retrieves the Meilisearch v0.20 documentation:
```sh theme={null}
git checkout v0.20
```
Visit the repository on GitHub to [view all documentation tags](https://github.com/meilisearch/documentation/tags).
## Access the documentation
There are different ways of accessing the documentation of previous Meilisearch releases depending on the version you checked out.
The site search bar is not functional in local copies of the documentation website.
### >= v1.2: read `.mdx` files
Starting with v1.2, Meilisearch's documentation content and build code live in separate repositories. Because of this, it is not possible to run a local copy of the documentation website.
To access the Meilisearch documentation for versions 1.2 and later, read the `.mdx` files directly, either locally with the help of a modern text editor or remotely using GitHub's interface.
### v0.17-v1.1: run a local Vuepress server
#### Install dependencies
This version of the Meilisearch documentation manages its dependencies with Yarn. Run the following command to install all required packages:
```sh theme={null}
yarn install
```
#### Start the local server
After installing dependencies, use Yarn to start the server:
```sh theme={null}
yarn dev
```
Yarn will build the website from the markdown source files. Once the server is online, use your browser to navigate to `http://localhost:8080`.
SDK code samples are not available in local copies of the documentation for Meilisearch v0.17 - v1.1.
### v0.11-v0.16: run a simple Python server
Accessing Meilisearch documentation from v0.11 to v0.16 requires launching an HTTP server on your local machine. Run the following command on your console:
```sh theme={null}
python3 -m http.server 8080
```
Once the server is online, use your browser to navigate to `http://localhost:8080`.
The above example uses Python to launch a local server, but alternatives such as `npx serve` work equally well.
### v0.8 to v0.10: read markdown source files
The build workflow on early versions of the documentation website involves multiple deprecated tools and libraries. Browse the source markdown files, either locally with the help of a modern text editor or remotely using GitHub's interface.
# Enterprise and Community editions
Source: https://www.meilisearch.com/docs/resources/self_hosting/enterprise_edition
Self-hosted users can choose between the Community Edition and the Enterprise Edition. The Community edition is free under the MIT license, while Enterprise offers advanced features under a BUSL license.
## What is the Meilisearch Community Edition?
The Meilisearch Community Edition (CE) is a free version of Meilisearch. It offers all essential Meilisearch features, such as full-text search and AI-powered search, under an MIT license.
## What is the Meilisearch Enterprise Edition?
The Enterprise Edition (EE) is a version of Meilisearch with advanced features. It is available under a BUSL license and cannot be freely used in production. EE is the Meilisearch version that powers Meilisearch Cloud.
The only feature exclusive to the Enterprise Edition is [sharding](/docs/resources/self_hosting/sharding/overview).
## When should you use each edition?
In most cases, using Meilisearch Cloud is the recommended way of integrating Meilisearch with your application.
Use the Meilisearch Community Edition when you want to host Meilisearch independently.
Meilisearch makes the Enterprise Edition binaries available for testing EE-only features before committing to a Meilisearch Cloud plan. If you want to self-host the Enterprise Edition in a production environment, [contact the sales team](mailto:sales@meilisearch.com).