# Documentation
Source: https://www.meilisearch.com/docs/home
Discover our guides, examples, and APIs to build fast and relevant search experiences with Meilisearch.
## Overview
Get an overview of Meilisearch features and philosophy.
See how Meilisearch compares to alternatives.
Use Meilisearch with your favorite language and framework.
## Use case demos
Take at look at example applications built with Meilisearch.
Search through multiple Eloquent models with Laravel.
Browse millions of products in our Nuxt 3 e-commerce demo app.
Search across the TMDB movies databases using Next.js.
# Which embedder should I choose?
Source: https://www.meilisearch.com/docs/learn/ai_powered_search/choose_an_embedder
General guidance on how to choose the embedder best suited for projects using AI-powered search.
Meilisearch officially supports many different embedders, such as OpenAI, Hugging Face, and Ollama, as well as the majority of embedding generators with a RESTful API.
This article contains general guidance on how to choose the embedder best suited for your project.
## When in doubt, choose OpenAI
OpenAI returns relevant search results across different subjects and datasets. It is suited for the majority of applications and Meilisearch actively supports and improves OpenAI functionality with every new release.
In the majority of cases, and especially if this is your first time working with LLMs and AI-powered search, choose OpenAI.
## If you are already using a specific AI service, choose the REST embedder
If you are already using a specific model from a compatible embedder, choose Meilisearch's REST embedder. This ensures you continue building upon tooling and workflows already in place with minimal configuration necessary.
## If dealing with non-textual content, choose the user-provided embedder
Meilisearch does not support searching images, audio, or any other content not presented as text. This limitation applies to both queries and documents. For example, Meilisearch's built-in embedder sources cannot search using an image instead of text. They also cannot use text to search for images without attached textual metadata.
In these cases, you will have to supply your own embeddings.
## Only choose Hugging Face when self-hosting small static datasets
Although it returns very relevant search results, the Hugging Face embedder must run directly in your server. This may lead to lower performance and extra costs when you are hosting Meilisearch in a service like DigitalOcean or AWS.
That said, Hugging Face can be a good embedder for datasets under 10k documents that you don't plan to update often.
Meilisearch Cloud does not support embedders with `{"source": "huggingFace"}`.
To implement Hugging Face embedders in the Cloud, use [HuggingFace inference points with the REST embedder](/guides/embedders/huggingface).
# Configure a REST embedder
Source: https://www.meilisearch.com/docs/learn/ai_powered_search/configure_rest_embedder
Create Meilisearch embedders using any provider with a REST API
You can integrate any text embedding generator with Meilisearch if your chosen provider offers a public REST API.
The process of integrating a REST embedder with Meilisearch varies depending on the provider and the way it structures its data. This guide shows you where to find the information you need, then walks you through configuring your Meilisearch embedder based on the information you found.
## Find your embedder provider's documentation
Each provider requires queries to follow a specific structure.
Before beginning to create your embedder, locate your provider's documentation for embedding creation. This should contain the information you need regarding API requests, request headers, and responses.
For example, [Mistral's embeddings documentation](https://docs.mistral.ai/api/#tag/embeddings) is part of their API reference. In the case of [Cloudflare's Workers AI](https://developers.cloudflare.com/workers-ai/models/bge-base-en-v1.5/#Parameters), expected input and response are tied to your chosen model.
## Set up the REST source and URL
Open your text editor and create an embedder object. Give it a name and set its source to `"rest"`:
```json
{
"EMBEDDER_NAME": {
"source": "rest"
}
}
```
Next, configure the URL Meilisearch should use to contact the embedding provider:
```json
{
"EMBEDDER_NAME": {
"source": "rest",
"url": "PROVIDER_URL"
}
}
```
Setting an embedder name, a `source`, and a `url` is mandatory for all REST embedders.
## Configure the data Meilisearch sends to the provider
Meilisearch's `request` field defines the structure of the input it will send to the provider. The way you must fill this field changes for each provider.
For example, Mistral expects two mandatory parameters: `model` and `input`. It also accepts one optional parameter: `encoding_format`. Cloudflare instead only expects a single field, `text`.
### Choose a model
In many cases, your provider requires you to explicitly set which model you want to use to create your embeddings. For example, in Mistral, `model` must be a string specifying a valid Mistral model.
Update your embedder object adding this field and its value:
```json
{
"EMBEDDER_NAME": {
"source": "rest",
"url": "PROVIDER_URL",
"request": {
"model": "MODEL_NAME"
}
}
}
```
In Cloudflare's case, the model is part of the API route itself and doesn't need to be specified in your `request`.
### The embedding prompt
The prompt corresponds to the data that the provider will use to generate your document embeddings. Its specific name changes depending on the provider you chose. In Mistral, this is the `input` field. In Cloudflare, it's called `text`.
Most providers accept either a string or an array of strings. A single string will generate one request per document in your database:
```json
{
"EMBEDDER_NAME": {
"source": "rest",
"url": "PROVIDER_URL",
"request": {
"model": "MODEL_NAME",
"input": "{{text}}"
}
}
}
```
`{{text}}` indicates Meilisearch should replace the contents of a field with your document data, as indicated in the embedder's [`documentTemplate`](/reference/api/settings#documenttemplate).
An array of strings allows Meilisearch to send up to 10 documents in one request, reducing the number of API calls to the provider:
```json
{
"EMBEDDER_NAME": {
"source": "rest",
"url": "PROVIDER_URL",
"request": {
"model": "MODEL_NAME",
"input": [
"{{text}}",
"{{..}}"
]
}
}
}
```
When using array prompts, the first item must be `{{text}}`. If you want to send multiple documents in a single request, the second array item must be `{{..}}`. When using `"{{..}}"`, it must be present in both `request` and `response`.
When using other embedding providers, `input` might be called something else, like `text` or `prompt`:
```json
{
"EMBEDDER_NAME": {
"source": "rest",
"url": "PROVIDER_URL",
"request": {
"model": "MODEL_NAME",
"text": "{{text}}"
}
}
}
```
### Provide other request fields
You may add as many fields to the `request` object as you need. Meilisearch will include them when querying the embeddings provider.
For example, Mistral allows you to optionally configure an `encoding_format`. Set it by declaring this field in your embedder's `request`:
```json
{
"EMBEDDER_NAME": {
"source": "rest",
"url": "PROVIDER_URL",
"request": {
"model": "MODEL_NAME",
"input": ["{{text}}", "{{..}}"],
"encoding_format": "float"
}
}
}
```
## The embedding response
You must indicate where Meilisearch can find the document embeddings in the provider's response. Consult your provider's API documentation, paying attention to where it places the embeddings.
Cloudflare's embeddings are located in an array inside `response.result.data`. Describe the full path to the embedding array in your embedder's `response`. The first array item must be `"{{embedding}}"`:
```json
{
"EMBEDDER_NAME": {
"source": "rest",
"url": "PROVIDER_URL",
"request": {
"text": "{{text}}"
},
"response": {
"result": {
"data": ["{{embedding}}"]
}
}
}
}
```
If the response contains multiple embeddings, use `"{{..}}"` as its second value:
```json
{
"EMBEDDER_NAME": {
"source": "rest",
"url": "PROVIDER_URL",
"request": {
"model": "MODEL_NAME",
"input": [
"{{text}}",
"{{..}}"
]
},
"response": {
"data": [
{
"embedding": "{{embedding}}"
},
"{{..}}"
]
}
}
}
```
When using `"{{..}}"`, it must be present in both `request` and `response`.
It is possible the response contains a single embedding outside of an array. Use `"{{embedding}}"` as its value:
```json
{
"EMBEDDER_NAME": {
"source": "rest",
"url": "PROVIDER_URL",
"request": {
"model": "MODEL_NAME",
"input": "{{text}}"
},
"response": {
"data": {
"text": "{{embedding}}"
}
}
}
}
```
It is also possible the response is a single item or array not nested in an object:
```json
{
"EMBEDDER_NAME": {
"source": "rest",
"url": "PROVIDER_URL",
"request": {
"model": "MODEL_NAME",
"input": [
"{{text}}",
"{{..}}"
]
},
"response": [
"{{embedding}}",
"{{..}}"
]
}
}
```
The prompt data type does not necessarily match the response data type. For example, Cloudflare always returns an array of embeddings, even if the prompt in your request was a string.
Meilisearch silently ignores `response` fields not pointing to an `"{{embedding}}"` value.
## The embedding header
Your provider might also request you to add specific headers to your request. For example, Azure's AI services require an `api-key` header containing an API key.
Add the `headers` field to your embedder object:
```json
{
"EMBEDDER_NAME": {
"source": "rest",
"url": "PROVIDER_URL",
"request": {
"text": "{{text}}"
},
"response": {
"result": {
"data": ["{{embedding}}"]
}
},
"headers": {
"FIELD_NAME": "FIELD_VALUE"
}
}
}
```
By default, Meilisearch includes a `Content-Type` header. It may also include an authorization bearer token, if you have supplied an API key.
## Configure remainder of the embedder
`source`, `request`, `response`, and `header` are the only fields specific to REST embedders.
Like other remote embedders, you're likely required to supply an `apiKey`:
```json
{
"EMBEDDER_NAME": {
"source": "rest",
"url": "PROVIDER_URL",
"request": {
"model": "MODEL_NAME",
"input": ["{{text}}", "{{..}}"],
"encoding_format": "float"
},
"response": {
"data": [
{
"embedding": "{{embedding}}"
},
"{{..}}"
]
},
"apiKey": "PROVIDER_API_KEY",
}
}
```
You should also set a `documentTemplate`. Good templates are short and include only highly relevant document data:
```json
{
"EMBEDDER_NAME": {
"source": "rest",
"url": "PROVIDER_URL",
"request": {
"model": "MODEL_NAME",
"input": ["{{text}}", "{{..}}"],
"encoding_format": "float"
},
"response": {
"data": [
{
"embedding": "{{embedding}}"
},
"{{..}}"
]
},
"apiKey": "PROVIDER_API_KEY",
"documentTemplate": "SHORT_AND_RELEVANT_DOCUMENT_TEMPLATE"
}
}
```
## Update your index settings
Now the embedder object is complete, update your index settings:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/embedders' \
-H 'Content-Type: application/json' \
--data-binary '{
"EMBEDDER_NAME": {
"source": "rest",
"url": "PROVIDER_URL",
"request": {
"model": "MODEL_NAME",
"input": ["{{text}}", "{{..}}"],
},
"response": {
"data": [
{
"embedding": "{{embedding}}"
},
"{{..}}"
]
},
"apiKey": "PROVIDER_API_KEY",
"documentTemplate": "SHORT_AND_RELEVANT_DOCUMENT_TEMPLATE"
}
}'
```
## Conclusion
In this guide you have seen a few examples of how to configure a REST embedder in Meilisearch. Though it used Mistral and Cloudflare, the general steps remain the same for all providers:
1. Find the provider's REST API documentation
2. Identify the embedding creation request parameters
3. Include parameters in your embedder's `request`
4. Identify the embedding creation response
5. Reproduce the path to the returned embeddings in your embedder's `response`
6. Add any required HTTP headers to your embedder's `header`
7. Update your index settings with the new embedder
# Differences between full-text and AI-powered search
Source: https://www.meilisearch.com/docs/learn/ai_powered_search/difference_full_text_ai_search
Meilisearch offers two types of search: full-text search and AI-powered search. This article explains their differences and intended use cases.
Meilisearch offers two types of search: full-text search and AI-powered search. This article explains their differences and intended use cases.
## Full-text search
This is Meilisearch's default search type. When performing a full-text search, Meilisearch checks the indexed documents for acceptable matches to a set of search terms. It is a fast and reliable search method.
For example, when searching for `"pink sandals"`, full-text search will only return clothing items explicitly mentioning these two terms. Searching for `"pink summer shoes for girls"` is likely to return fewer and less relevant results.
## AI-powered search
AI-powered search is Meilisearch's newest search method. It returns results based on a query's meaning and context.
AI-powered search uses LLM providers such as OpenAI and Hugging Face to generate vector embeddings representing the meaning and context of both query terms and documents. It then compares these vectors to find semantically similar search results.
When using AI-powered search, Meilisearch returns both full-text and semantic results by default. This is also called hybrid search.
With AI-powered search, searching for `"pink sandals"` will be more efficient, but queries for `"cute pink summer shoes for girls"` will still return relevant results including light-colored open shoes.
## Use cases
Full-text search is a reliable choice that works well in most scenarios. It is fast, less resource-intensive, and requires no extra configuration. It is best suited for situations where you need precise matches to a query and your users are familiar with the relevant keywords.
AI-powered search combines the flexibility of semantic search with the performance of full-text search. Most searches, whether short and precise or long and vague, will return very relevant search results. In most cases, AI-powered search will offer your users the best search experience, but will require extra configuration. AI-powered search may also entail extra costs if you use a third-party service such as OpenAI to generate vector embeddings.
# Document template best practices
Source: https://www.meilisearch.com/docs/learn/ai_powered_search/document_template_best_practices
This guide shows you what to do and what to avoid when writing a `documentTemplate`.
When using AI-powered search, Meilisearch generates prompts by filling in your embedder's `documentTemplate` with each document's data. The better your prompt is, the more relevant your search results.
This guide shows you what to do and what to avoid when writing a `documentTemplate`.
## Sample document
Take a look at this document from a database of movies:
```json
{
"id": 2,
"title": "Ariel",
"overview": "Taisto Kasurinen is a Finnish coal miner whose father has just committed suicide and who is framed for a crime he did not commit. In jail, he starts to dream about leaving the country and starting a new life. He escapes from prison but things don't go as planned...",
"genres": [
"Drama",
"Crime",
"Comedy"
],
"poster": "https://image.tmdb.org/t/p/w500/ojDg0PGvs6R9xYFodRct2kdI6wC.jpg",
"release_date": 593395200
}
```
## Do not use the default `documentTemplate`
Use a custom `documentTemplate` value in your embedder configuration.
The default `documentTemplate` includes all searchable fields with non-`null` values. In most cases, this adds noise and more information than the embedder needs to provide relevant search results.
## Only include highly relevant information
Take a look at your document and identify the most relevant fields. A good `documentTemplate` for the sample document could be:
```
"A movie called {{doc.title}} about {{doc.overview}}"
```
In the sample document, `poster` and `id` contain data that has little semantic importance and can be safely excluded. The data in `genres` and `release_date` is very useful for filters, but say little about this specific film.
This leaves two relevant fields: `title` and `overview`.
## Keep prompts short
For the best results, keep prompts somewhere between 15 and 45 words:
```
"A movie called {{doc.title}} about {{doc.overview | truncatewords: 20}}"
```
In the sample document, the `overview` alone is 49 words. Use Liquid's [`truncate`](https://shopify.github.io/liquid/filters/truncate/) or [`truncatewords`](https://shopify.github.io/liquid/filters/truncatewords/) to shorten it.
Short prompts do not have enough information for the embedder to properly understand the query context. Long prompts instead provide too much information and make it hard for the embedder to identify what is truly relevant about a document.
## Add guards for missing fields
Some documents might not contain all the fields you expect. If your template directly references a missing field, Meilisearch will throw an error when indexing documents.
To prevent this, use Liquid’s `if` statements to add guards around fields:
```
{% if doc.title %}
A movie called {{ doc.title }}
{% endif %}
```
This ensures the template only tries to include data that already exists in a document. If a field is missing, the embedder still receives a valid and useful prompt without errors.
## Conclusion
In this article you saw the main steps to generating prompts that lead to relevant AI-powered search results:
* Do not use the default `documentTemplate`
* Only include relevant data
* Truncate long fields
* Add guards for missing fields
# Getting started with AI-powered search
Source: https://www.meilisearch.com/docs/learn/ai_powered_search/getting_started_with_ai_search
AI-powered search uses LLMs to retrieve search results. This tutorial shows you how to configure an OpenAI embedder and perform your first search.
[AI-powered search](https://meilisearch.com/solutions/vector-search), sometimes also called vector search or hybrid search, uses [large language models (LLMs)](https://en.wikipedia.org/wiki/Large_language_model) to retrieve search results based on the meaning and context of a query.
This tutorial will walk you through configuring AI-powered search in your Meilisearch project. You will see how to set up an embedder with OpenAI, generate document embeddings, and perform your first search.
## Requirements
* A running Meilisearch project
* An [OpenAI API key](https://platform.openai.com/api-keys)
* A command-line console
## Create a new index
First, create a new Meilisearch project. If this is your first time using Meilisearch, follow the [quick start](/learn/getting_started/cloud_quick_start) then come back to this tutorial.
Next, create a `kitchenware` index and add [this kitchenware products dataset](/assets/datasets/kitchenware.json) to it. It will take Meilisearch a few moments to process your request, but you can continue to the next step while your data is indexing.
## Generate embeddings with OpenAI
In this step, you will configure an OpenAI embedder. Meilisearch uses **embedders** to translate documents into **embeddings**, which are mathematical representations of a document's meaning and context.
Open a blank file in your text editor. You will only use this file to build your embedder one step at a time, so there's no need to save it if you plan to finish the tutorial in one sitting.
### Choose an embedder name
In your blank file, create your `embedder` object:
```json
{
"products-openai": {}
}
```
`products-openai` is the name of your embedder for this tutorial. You can name embedders any way you want, but try to keep it simple, short, and easy to remember.
### Choose an embedder source
Meilisearch relies on third-party services to generate embeddings. These services are often referred to as the embedder source.
Add a new `source` field to your embedder object:
```json
{
"products-openai": {
"source": "openAi"
}
}
```
Meilisearch supports several embedder sources. This tutorial uses OpenAI because it is a good option that fits most use cases.
### Choose an embedder model
Models supply the information required for embedders to process your documents.
Add a new `model` field to your embedder object:
```json
{
"products-openai": {
"source": "openAi",
"model": "text-embedding-3-small"
}
}
```
Each embedder service supports different models targeting specific use cases. `text-embedding-3-small` is a cost-effective model for general usage.
### Create your API key
Log into OpenAI, or create an account if this is your first time using it. Generate a new API key using [OpenAI's web interface](https://platform.openai.com/api-keys).
Add the `apiKey` field to your embedder:
```json
{
"products-openai": {
"source": "openAi",
"model": "text-embedding-3-small",
"apiKey": "OPEN_AI_API_KEY",
}
}
```
Replace `OPEN_AI_API_KEY` with your own API key.
You may use any key tier for this tutorial. Use at least [Tier 2 keys](https://platform.openai.com/docs/guides/rate-limits/usage-tiers?context=tier-two) in production environments.
### Design a prompt template
Meilisearch embedders only accept textual input, but documents can be complex objects containing different types of data. This means you must convert your documents into a single text field. Meilisearch uses [Liquid](https://shopify.github.io/liquid/basics/introduction/), an open-source templating language to help you do that.
A good template should be short and only include the most important information about a document. Add the following `documentTemplate` to your embedder:
```json
{
"products-openai": {
"source": "openAi",
"model": "text-embedding-3-small",
"apiKey": "OPEN_AI_API_KEY",
"documentTemplate": "An object used in a kitchen named '{{doc.name}}'"
}
}
```
This template starts by giving the general context of the document: `An object used in a kitchen`. Then it adds the information that is specific to each document: `doc` represents your document, and you can access any of its attributes using dot notation. `name` is an attribute with values such as `wooden spoon` or `rolling pin`. Since it is present in all documents in this dataset and describes the product in few words, it is a good choice to include in the template.
### Create the embedder
Your embedder object is ready. Send it to Meilisearch by updating your index settings:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/indexes/kitchenware/settings/embedders' \
-H 'Content-Type: application/json' \
--data-binary '{
"products-openai": {
"source": "openAi",
"apiKey": "OPEN_AI_API_KEY",
"model": "text-embedding-3-small",
"documentTemplate": "An object used in a kitchen named '{{doc.name}}'"
}
}'
```
Replace `MEILISEARCH_URL` with the address of your Meilisearch project, and `OPEN_AI_API_KEY` with your [OpenAI API key](https://platform.openai.com/api-keys).
Meilisearch and OpenAI will start processing your documents and updating your index. This may take a few moments, but once it's done you are ready to perform an AI-powered search.
## Perform an AI-powered search
AI-powered searches are very similar to basic text searches. You must query the `/search` endpoint with a request containing both the `q` and the `hybrid` parameters:
```sh
curl \
-X POST 'MEILISEARCH_URL/indexes/kitchenware/search' \
-H 'content-type: application/json' \
--data-binary '{
"q": "kitchen utensils made of wood",
"hybrid": {
"embedder": "products-openai"
}
}'
```
For this tutorial, `hybrid` is an object with a single `embedder` field.
Meilisearch will then return an equal mix of semantic and full-text matches.
## Conclusion
Congratulations! You have created an index, added a small dataset to it, and activated AI-powered search. You then used OpenAI to generate embeddings out of your documents, and performed your first AI-powered search.
## Next steps
Now you have a basic overview of the basic steps required for setting up and performing AI-powered searches, you might want to try and implement this feature in your own application.
For practical information on implementing AI-powered search with other services, consult our [guides section](/guides/embedders/openai). There you will find specific instructions for embedders such as [LangChain](/guides/langchain) and [Cloudflare](/guides/embedders/cloudflare).
For more in-depth information, consult the API reference for [embedder settings](/reference/api/settings#embedders) and [the `hybrid` search parameter](/reference/api/search#hybrid-search).
# Image search with user-provided embeddings
Source: https://www.meilisearch.com/docs/learn/ai_powered_search/image_search_with_user_provided_embeddings
This article shows you the main steps for performing multimodal text-to-image searches
This article shows you the main steps for performing multimodal searches where you can use text to search through a database of images with no associated metadata.
## Requirements
* A database of images
* A Meilisearch project
* An embedding generation provider you can install locally
## Configure your local embedding generation pipeline
First, set up a system that sends your images to your chosen embedding generation provider, then integrates the returned embeddings into your dataset.
The exact procedure depends heavily on your specific setup, but should include these main steps:
1. Choose a provider you can run locally
2. Choose a model that supports both image and text input
3. Send your images to the embedding generation provider
4. Add the returned embeddings to the `_vector` field for each image in your database
In most cases your system should run these steps periodically or whenever you update your database.
## Configure a user-provided embedder
Configure the `embedder` index setting, settings its source to `userProvided`:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/indexes/movies/settings' \
-H 'Content-Type: application/json' \
--data-binary '{
"embedders": {
"EMBEDDER_NAME": {
"source": "userProvided",
"dimensions": MODEL_DIMENSIONS
}
}
}'
```
Replace `EMBEDDER_NAME` with the name you wish to give your embedder. Replace `MODEL_DIMENSIONS` with the number of dimensions of your chosen model.
## Add documents to Meilisearch
Next, use [the `/documents` endpoint](/reference/api/documents) to upload the vectorized images.
In most cases, you should automate this step so Meilisearch is up to date with your primary database.
## Set up pipeline for vectorizing queries
Since you are using a `userProvided` embedder, you must also generate the embeddings for the search query. This process should be similar to generating embeddings for your images:
1. Receive user query from your front-end
2. Send query to your local embedding generation provider
3. Perform search using the returned query embedding
## Vector search with user-provided embeddings
Once you have the query's vector, pass it to the `vector` search parameter to perform a semantic AI-powered search:
```sh
curl -X POST -H 'content-type: application/json' \
'localhost:7700/indexes/products/search' \
--data-binary '{
"vector": VECTORIZED_QUERY,
"hybrid": {
"embedder": "EMBEDDER_NAME",
}
}'
```
Replace `VECTORIZED_QUERY` with the embedding generated by your provider and `EMBEDDER_NAME` with your embedder.
If your images have any associated metadata, you may perform a hybrid search by including the original `q`:
```sh
curl -X POST -H 'content-type: application/json' \
'localhost:7700/indexes/products/search' \
--data-binary '{
"vector": VECTORIZED_QUERY,
"hybrid": {
"embedder": "EMBEDDER_NAME",
}
"q": "QUERY",
}'
```
## Conclusion
You have seen the main steps for implementing image search with Meilisearch:
1. Prepare a pipeline that converts your images into vectors
2. Index the vectorized images with Meilisearch
3. Prepare a pipeline that converts your users' queries into vectors
4. Perform searches using the converted queries
# Retrieve related search results
Source: https://www.meilisearch.com/docs/learn/ai_powered_search/retrieve_related_search_results
This guide shows you how to use the similar documents endpoint to create an AI-powered movie recommendation workflow.
# Retrieve related search results
This guide shows you how to use the [similar documents endpoint](/reference/api/similar) to create an AI-powered movie recommendation workflow.
First, you will create an embedder and add documents to your index. You will then perform a search, and use the top result's primary key to retrieve similar movies in your database.
## Prerequisites
* A running Meilisearch project
* A [tier >=2](https://platform.openai.com/docs/guides/rate-limits#usage-tiers) OpenAI API key
## Create a new index
Create an index called `movies` and add this `movies.json` dataset to it. If necessary, consult the [getting started](/learn/getting_started/cloud_quick_start) for more instructions on index creation.
Each document in the dataset represents a single movie and has the following structure:
* `id`: a unique identifier for each document in the database
* `title`: the title of the movie
* `overview`: a brief summary of the movie's plot
* `genres`: an array of genres associated with the movie
* `poster`: a URL to the movie's poster image
* `release_date`: the release date of the movie, represented as a Unix timestamp
## Configure an embedder
Next, use the Cloud UI to configure an OpenAI embedder:
You may also use the `/settings/embedders` API subroute to configure your embedder:
Replace `MEILISEARCH_URL`, `MEILISEARCH_API_KEY`, and `OPENAI_API_KEY` with the corresponding values in your application.
Meilisearch will start generating the embeddings for all movies in your dataset. Use the returned `taskUid` to [track the progress of this task](/learn/async/asynchronous_operations). Once it is finished, you are ready to start searching.
## Perform a hybrid search
With your documents added and all embeddings generated, you can perform a search:
This request returns a list of movies. Pick the top result and take note of its primary key in the `id` field. In this case, it's the movie "Batman" with `id` 192.
## Return similar documents
Pass "Batman"'s `id` to your index's [`/similar` route](/reference/api/similar), specifying `movies-text` as your embedder:
Meilisearch will return a list of the 20 documents most similar to the movie you chose. You may then choose to display some of these similar results to your users, pointing them to other movies that may also interest them.
## Conclusion
Congratulations! You have successfully built an AI-powered movie search and recommendation system using Meilisearch by:
* Setting up a Meilisearch project and configured it for AI-powered search
* Implementing hybrid search combining keyword and semantic search capabilities
* Integrating Meilisearch's similarity search for movie recommendations
In a real-life application, you would now start integrating this workflow into a front end, like the one in this [official Meilisearch blog post](https://www.meilisearch.com/blog/add-ai-powered-search-to-react).
# Use AI-powered search with user-provided embeddings
Source: https://www.meilisearch.com/docs/learn/ai_powered_search/search_with_user_provided_embeddings
This guide shows how to perform AI-powered searches with user-generated embeddings instead of relying on a third-party tool.
This guide shows how to perform AI-powered searches with user-generated embeddings instead of relying on a third-party tool.
## Requirements
* A Meilisearch project
## Configure a custom embedder
Configure the `embedder` index setting, settings its source to `userProvided`:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/indexes/movies/settings' \
-H 'Content-Type: application/json' \
--data-binary '{
"embedders": {
"image2text": {
"source": "userProvided",
"dimensions": 3
}
}
}'
```
## Add documents to Meilisearch
Next, use [the `/documents` endpoint](/reference/api/documents?utm_campaign=vector-search\&utm_source=docs\&utm_medium=vector-search-guide) to upload vectorized documents. Place vector data in your documents' `_vectors` field:
```sh
curl -X POST -H 'content-type: application/json' \
'localhost:7700/indexes/products/documents' \
--data-binary '[
{ "id": 0, "_vectors": {"image2text": [0, 0.8, -0.2]}, "text": "frying pan" },
{ "id": 1, "_vectors": {"image2text": [1, -0.2, 0]}, "text": "baking dish" }
]'
```
## Vector search with user-provided embeddings
When using a custom embedder, you must vectorize both your documents and user queries.
Once you have the query's vector, pass it to the `vector` search parameter to perform an AI-powered search:
```sh
curl -X POST -H 'content-type: application/json' \
'localhost:7700/indexes/products/search' \
--data-binary '{ "vector": [0, 1, 2] }'
```
`vector` must be an array of numbers indicating the search vector. You must generate these yourself when using vector search with user-provided embeddings.
`vector` can be used together with [other search parameters](/reference/api/search?utm_campaign=vector-search\&utm_source=docs\&utm_medium=vector-search-guide), including [`filter`](/reference/api/search#filter) and [`sort`](/reference/api/search#sort):
```sh
curl -X POST -H 'content-type: application/json' \
'localhost:7700/indexes/products/search' \
--data-binary '{
"vector": [0, 1, 2],
"filter": "price < 10",
"sort": ["price:asc"]
}'
```
# Bind search analytics events to a user
Source: https://www.meilisearch.com/docs/learn/analytics/bind_events_user
This guide shows you how to manually differentiate users across search analytics using the X-MS-USER-ID HTTP header.
By default, Meilisearch uses IP addresses to identify users and calculate the total user metrics. This guide shows you how to use the `X-MS-USER-ID` HTTP header to manually link analytics events to specific users.
This is useful if you're searching from your back end, as all searches would otherwise appear to come from your server's IP address, making it difficult to accurately track the number of individual users.
## Requirements
* A Meilisearch Cloud project with analytics and monitoring enabled
* A working pipeline for submitting analytics events
## Add `X-MS-USER-ID` to your search query
Include the `X-MS-USER-ID` header in your search requests:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/INDEX_NAME/search' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer DEFAULT_SEARCH_API_KEY' \
-H 'X-MS-USER-ID: MEILISEARCH_USER_ID' \
--data-binary '{}'
```
Replace `MEILISEARCH_USER_ID` with any value that uniquely identifies that user. This may be an authenticated user's ID when running searches from your own back end, or a hash of the user's IP address.
## Add `X-MS-USER-ID` to the analytics event
Next, submit your analytics event to the analytics endpoint. Send the same header and value in your API call:
```bash cURL
curl \
-X POST 'https://edge.meilisearch.com/events' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer DEFAULT_SEARCH_API_KEY' \
-H 'X-MS-USER-ID: MEILISEARCH_USER_ID' \
--data-binary '{
"eventType": "click",
"eventName": "Search Result Clicked",
"indexUid": "products",
"objectId": "0",
"position": 0
}'
```
## Conclusion
In this guide you have seen how to bind analytics events to specific users by specifying the same HTTP header for both the search request and the analytics event.
# Configure search analytics
Source: https://www.meilisearch.com/docs/learn/analytics/configure_analytics
Meilisearch Cloud offers in-depth search analytics to help you understand how users search in your application.
Enable Meilisearch Cloud analytics to help you understand how users search in your application.
This guide walks you through activating analytics, updating your project URL, and configuring all data points.
## Requirements
You must have a [Meilisearch Cloud](https://meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=analytics) account to access search analytics.
## Enable analytics in the project overview
Log into your Meilisearch Cloud account and navigate to your project's overview. Find the "Analytics and monitoring" section and click on the "Enable analytics and monitoring" button:
Meilisearch Cloud will begin processing your request. The "Analytics and monitoring" section will update when the feature is enabled.
Activating analytics will automatically activate [monitoring](/learn/analytics/configure_monitoring).
## Update URLs in your application
When you enable analytics, Meilisearch Cloud changes your project's API URL. Meilisearch Cloud is only able to track metrics for queries sent to this URL.
Update your application so all API requests point to the new URL:
```sh
curl \
-X POST 'https://edge.meilisearch.com/indexes/products/search' \
-H 'Content-Type: application/json' \
--data-binary '{ "q": "green socks" }'
```
**The previous API URL will remain functional**, but requests targeting it will not send any data to the analytics interface. If you created any custom API keys using the previous URL, you will need to replace them.
## Configure click-through rate and average click position
To track metrics like click-through rate and average click position, Meilisearch Cloud needs to know when users click on search results.
Every time a user clicks on a search result, your application must send a `click` event to the `POST` endpoint of Meilisearch Cloud analytics route:
```bash cURL
curl \
-X POST 'https://edge.meilisearch.com/events' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer DEFAULT_SEARCH_API_KEY' \
--data-binary '{
"eventType": "click",
"eventName": "Search Result Clicked",
"indexUid": "products",
"objectId": "0",
"position": 0
}'
```
By default, Meilisearch associates analytics events with the most recent search of the user who triggered them.
For more information, consult the [analytics events endpoint reference](/learn/analytics/events_endpoint#the-conversion-event-object).
## Configure conversion rate
To track conversion rate, first identify what should count as a conversion for your application. For example, in a web shop, a conversion might be a user finalizing the checkout process.
Once you have established what counts as a conversion in your application, configure it to send a `conversion` event to the `POST` endpoint of Meilisearch Cloud analytics route:
```bash cURL
curl \
-X POST 'https://edge.meilisearch.com/events' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer DEFAULT_SEARCH_API_KEY'
--data-binary '{
"eventType": "conversion",
"eventName": "Product Added To Cart",
"indexUid": "products",
"objectId": "0",
"position": 0
}'
```
By default, Meilisearch associates analytics events with the most recent search of the user who triggered them.
It is not possible to associate multiple `conversion` events with the same search.
For more information, consult the [analytics events endpoint reference](/learn/analytics/events_endpoint#the-conversion-event-object).
# Configure application monitoring metrics
Source: https://www.meilisearch.com/docs/learn/analytics/configure_monitoring
Meilisearch Cloud offers in-depth metrics to help monitor your application performance.
Enable Meilisearch Cloud monitoring to keep track of application performance and service status.
## Requirements
You must have a [Meilisearch Cloud](https://meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=monitoring) account to access monitoring metrics.
## Enable monitoring in the project overview
Log into your Meilisearch Cloud account and navigate to your project's overview. Find the "Analytics and monitoring" section and click on the "Enable analytics and monitoring" button:
Meilisearch Cloud will begin processing your request. The "Analytics and monitoring" section will update with new instruction text and buttons when the feature is enabled.
Activating monitoring will automatically activate [analytics](/learn/analytics/configure_analytics).
## Update URLs in your application
When you enable monitoring, Meilisearch Cloud changes your project's API URL. Meilisearch Cloud is only able to track metrics for queries sent to this URL.
Update your application so all API requests point to the new URL:
```sh
curl \
-X POST 'http://edge.meilisearch.com/indexes/products/search' \
-H 'Content-Type: application/json' \
--data-binary '{ "q": "green socks" }'
```
**The previous API URL will remain functional**, but requests targeting it will not send any data to the monitoring interface.
# Deactivate search analytics and monitoring
Source: https://www.meilisearch.com/docs/learn/analytics/deactivate_analytics_monitoring
Meilisearch Cloud offers in-depth search analytics to help you understand how users search in your application.
This guide shows you how to deactivate Meilisearch Cloud's search analytics and monitoring.
## Disable analytics and monitoring in the project overview
Log into your Meilisearch Cloud account and navigate to your project's overview. Find the "Analytics and monitoring" section and press the "Disable analytics and monitoring" button:
## Update URLs in your application
Disabling analytics and monitoring changes your API URL. Update your application so all API requests point to the correct URL:
```sh
curl \
-X POST 'https://PROJECT_URL/indexes/products/search' \
-H 'Content-Type: application/json' \
--data-binary '{ "q": "green socks" }'
```
**The previous API URL will remain functional**, but Meilisearch recommends not using it after disabling analytics in your project. If you created any custom API keys using the previous URL, you will need to replace them.
## Update `conversion` and `click` events
If you were tracking `conversion` and `click` events, update your application to stop sending them to Meilisearch Cloud.
# Analytics events endpoint
Source: https://www.meilisearch.com/docs/learn/analytics/events_endpoint
This reference describes /events, the endpoint you should use to submit analytics events to Meilisearch Cloud. It also describes the accepted event objects and the data you must include in them.
This reference describes `/events`, the endpoint you should use to submit analytics events to Meilisearch Cloud. It also describes the accepted event objects and the data you must include in them.
## The `/events` endpoint
The `/events` endpoint is only available for Meilisearch Cloud projects with analytics and monitoring activated.
### Send an event
```http
POST https://edge.meilisearch.com/events
```
Send an analytics event to Meilisearch Cloud. Accepts [`click`](#the-click-event-object) and [`conversion`](#the-conversion-event-object) events.
By default, Meilisearch associates analytics events with the most recent search of the user who triggered them. Include the same `X-MS-USER-ID` header in your search and event requests to manually [bind analytics events to a user](/learn/analytics/bind_events_user).
#### Example
##### Response: `201 Created`
### The `click` event object
The `click` event must deliver an object with the following fields:
```json
{
"eventType": "click",
"eventName": "Search Result Clicked",
"indexUid": "products",
"objectId": "0",
"position": 0
}
```
* `eventType`: a string indicating this is a `click` event
* `eventName`: a string describing the event
* `indexUid`: a string indicating the clicked document's index
* `objectId`: a string indicating the clicked document's primary key
* `position`: an integer indicating the clicked document's position in the list of search results
### The `conversion` event object
The `conversion` event must deliver an object with the following fields:
```json
{
"eventType": "conversion",
"eventName": "Product Added To Cart",
"indexUid": "products",
"objectID": "0",
"position": 0
}
```
* `eventType`: indicates this is a `conversion` event
* `eventName`: a string describing the event
* `indexUid`: the document's index
* `objectID`: the document's primary key
* `position`: the document's position in the list of search results
# Tasks and asynchronous operations
Source: https://www.meilisearch.com/docs/learn/async/asynchronous_operations
Meilisearch uses a task queue to handle asynchronous operations. This in-depth guide explains tasks, their uses, and how to manage them using Meilisearch's API.
Many operations in Meilisearch are processed **asynchronously**. These API requests are not handled immediately—instead, Meilisearch places them in a queue and processes them in the order they were received.
## Which operations are asynchronous?
Every operation that might take a long time to be processed is handled asynchronously. Processing operations asynchronously allows Meilisearch to handle resource-intensive tasks without impacting search performance.
Currently, these are Meilisearch's asynchronous operations:
* Creating an index
* Updating an index
* Swapping indexes
* Deleting an index
* Updating index settings
* Adding documents to an index
* Updating documents in an index
* Deleting documents from an index
* Canceling a task
* Deleting a task
* Creating a dump
* Creating snapshots
## Understanding tasks
When an API request triggers an asynchronous process, Meilisearch creates a task and places it in a [task queue](#task-queue).
### Task objects
Tasks are objects containing information that allow you to track their progress and troubleshoot problems when things go wrong.
A [task object](/reference/api/tasks#task-object) includes data not present in the original request, such as when the request was enqueued, the type of request, and an error code when the task fails:
```json
{
"uid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 67493,
"indexedDocuments": null
},
"error": null,
"duration": null,
"enqueuedAt": "2021-08-10T14:29:17.000000Z",
"startedAt": null,
"finishedAt": null
}
```
For a comprehensive description of each task object field, consult the [task API reference](/reference/api/tasks).
#### Summarized task objects
When you make an API request for an asynchronous operation, Meilisearch returns a [summarized version](/reference/api/tasks#summarized-task-object) of the full `task` object.
```json
{
"taskUid": 0,
"indexUid": "movies",
"status": "enqueued",
"type": "indexCreation",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
Use the summarized task's `taskUid` to [track the progress of a task](/reference/api/tasks#get-one-task).
#### Task `status`
Tasks always contain a field indicating the task's current `status`. This field has one of the following possible values:
* **`enqueued`**: the task has been received and will be processed soon
* **`processing`**: the task is being processed
* **`succeeded`**: the task has been successfully processed
* **`failed`**: a failure occurred when processing the task. No changes were made to the database
* **`canceled`**: the task was canceled
`succeeded`, `failed`, and `canceled` tasks are finished tasks. Meilisearch keeps them in the task database but has finished processing these tasks. It is possible to [configure a webhook](/reference/api/webhooks) to notify external services when a task is finished.
`enqueued` and `processing` tasks are unfinished tasks. Meilisearch is either processing them or will do so in the future.
#### Global tasks
Some task types are not associated with a particular index but apply to the entire instance. These tasks are called global tasks. Global tasks always display `null` for the `indexUid` field.
Meilisearch considers the following task types as global:
* [`dumpCreation`](/reference/api/tasks#dumpcreation)
* [`taskCancelation`](/reference/api/tasks#taskcancelation)
* [`taskDeletion`](/reference/api/tasks#taskdeletion)
* [`indexSwap`](/reference/api/tasks#indexswap)
* [`snapshotCreation`](/reference/api/tasks#snapshotcreation)
In a protected instance, your API key must have access to all indexes (`"indexes": [*]`) to view global tasks.
### Task queue
After creating a task, Meilisearch places it in a queue. Enqueued tasks are processed one at a time, following the order in which they were requested.
When the task queue reaches its limit (about 10GiB), it will throw a `no_space_left_on_device` error. Users will need to delete tasks using the [delete tasks endpoint](/reference/api/tasks#delete-tasks) to continue write operations.
#### Task queue priority
Meilisearch considers certain tasks high-priority and always places them at the front of the queue.
The following types of tasks are always processed as soon as possible:
1. `taskCancelation`
2. `taskDeletion`
3. `snapshotCreation`
4. `dumpCreation`
All other tasks are processed in the order they were enqueued.
## Task workflow
When you make a [request for an asynchronous operation](#which-operations-are-asynchronous), Meilisearch processes all tasks following the same steps:
1. Meilisearch creates a task, puts it in the task queue, and returns a [summarized `task` object](/learn/async/asynchronous_operations#summarized-task-objects). Task `status` set to `enqueued`
2. When your task reaches the front of the queue, Meilisearch begins working on it. Task `status` set to `processing`
3. Meilisearch finishes the task. Status set to `succeeded` if task was successfully processed, or `failed` if there was an error
**Terminating a Meilisearch instance in the middle of an asynchronous operation is completely safe** and will never adversely affect the database.
### Task batches
Meilisearch processes tasks in batches, grouping tasks for the best possible performance. In most cases, batching should be transparent and have no impact on the overall task workflow. Use [the `/batches` route](/reference/api/batches) to obtain more information on batches and how they are processing your tasks.
### Canceling tasks
You can cancel a task while it is `enqueued` or `processing` by using [the cancel tasks endpoint](/reference/api/tasks#cancel-tasks). Doing so changes a task's `status` to `canceled`.
Tasks are not canceled when you terminate a Meilisearch instance. Meilisearch discards all progress made on `processing` tasks and resets them to `enqueued`. Task handling proceeds as normal once the instance is relaunched.
### Deleting tasks
[Finished tasks](#task-status) remain visible in [the task list](/reference/api/tasks#get-tasks). To delete them manually, use the [delete tasks route](/reference/api/tasks#delete-tasks).
Meilisearch stores up to 1M tasks in the task database. If enqueuing a new task would exceed this limit, Meilisearch automatically tries to delete the oldest 100K finished tasks. If there are no finished tasks in the database, Meilisearch does not delete anything and enqueues the new task as usual.
#### Examples
Suppose you add a new document to your instance using the [add documents endpoint](/reference/api/documents#add-or-replace-documents) and receive a `taskUid` in response.
When you query the [get task endpoint](/reference/api/tasks#get-one-task) using this value, you see that it has been `enqueued`:
```json
{
"uid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 67493,
"indexedDocuments": null
},
"error": null,
"duration": null,
"enqueuedAt": "2021-08-10T14:29:17.000000Z",
"startedAt": null,
"finishedAt": null
}
```
Later, you check the task's progress one more time. It was successfully processed and its `status` changed to `succeeded`:
```json
{
"uid": 1,
"indexUid": "movies",
"status": "succeeded",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
"receivedDocuments": 67493,
"indexedDocuments": 67493
},
"error": null,
"duration": "PT1S",
"enqueuedAt": "2021-08-10T14:29:17.000000Z",
"startedAt": "2021-08-10T14:29:18.000000Z",
"finishedAt": "2021-08-10T14:29:19.000000Z"
}
```
Had the task failed, the response would have included a detailed `error` object:
```json
{
"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": "internal",
"type": "missing_document_id",
"link": "https://docs.meilisearch.com/errors#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"
}
```
If the task had been [canceled](/reference/api/tasks#cancel-tasks) while it was `enqueued` or `processing`, it would have the `canceled` status and a non-`null` value for the `canceledBy` field.
After a task has been [deleted](/reference/api/tasks#delete-tasks), trying to access it returns a [`task_not_found`](/reference/errors/error_codes#task_not_found) error.
# Filtering tasks
Source: https://www.meilisearch.com/docs/learn/async/filtering_tasks
This guide shows you how to use query parameters to filter tasks and obtain a more readable list of asynchronous operations.
Querying the [get tasks endpoint](/reference/api/tasks#get-tasks) returns all tasks that have not been deleted. This unfiltered list may be difficult to parse in large projects.
This guide shows you how to use query parameters to filter tasks and obtain a more readable list of asynchronous operations.
Filtering batches with [the `/batches` route](/reference/api/batches) follows the same rules as filtering tasks. Keep in mind that many `/batches` parameters such as `uids` target the tasks included in batches, instead of the batches themselves.
## Requirements
* a command-line terminal
* a running Meilisearch project
## Filtering tasks with a single parameter
Use the get tasks endpoint to fetch all `canceled` tasks:
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/tasks?statuses=failed'
```
```javascript JS
client.tasks.getTasks({ statuses: ['failed', 'canceled'] })
```
```python Python
client.get_tasks({'statuses': ['failed', 'canceled']})
```
```php PHP
$client->getTasks((new TasksQuery())->setStatuses(['failed', 'canceled']));
```
```java Java
TasksQuery query = new TasksQuery().setStatuses(new String[] {"failed", "canceled"});
client.getTasks(query);
```
```ruby Ruby
client.get_tasks(statuses: ['failed', 'canceled'])
```
```go Go
client.GetTasks(&meilisearch.TasksQuery{
Statuses: []meilisearch.TaskStatus{
meilisearch.TaskStatusFailed,
meilisearch.TaskStatusCanceled,
},
})
```
```csharp C#
await client.GetTasksAsync(new TasksQuery { Statuses = new List { TaskInfoStatus.Failed, TaskInfoStatus.Canceled } });
```
```rust Rust
let mut query = TasksQuery::new(&client);
let tasks = query
.with_statuses(["failed"])
.execute()
.await
.unwrap();
```
```swift Swift
client.getTasks(params: TasksQuery(statuses: [.failed, .canceled])) { result in
switch result {
case .success(let taskResult):
print(taskResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.getTasks(
params: TasksQuery(
statuses: ['failed', 'canceled'],
),
);
```
Use a comma to separate multiple values and fetch both `canceled` and `failed` tasks:
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/tasks?statuses=failed,canceled'
```
```rust Rust
let mut query = TasksQuery::new(&client);
let tasks = query
.with_statuses(["failed", "canceled"])
.execute()
.await
.unwrap();
```
You may filter tasks based on `uid`, `status`, `type`, `indexUid`, `canceledBy`, or date. Consult the API reference for a full list of task filtering parameters.
## Combining filters
Use the ampersand character (`&`) to combine filters, equivalent to a logical `AND`:
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/tasks?indexUids=movies&types=documentAdditionOrUpdate,documentDeletion&statuses=processing'
```
```javascript JS
client.tasks.getTasks({
indexUids: ['movies'],
types: ['documentAdditionOrUpdate','documentDeletion'],
statuses: ['processing']
})
```
```python Python
client.get_tasks(
{
'indexUids': 'movies',
'types': ['documentAdditionOrUpdate', 'documentDeletion'],
'statuses': ['processing'],
}
)
```
```php PHP
$client->getTasks(
(new TasksQuery())
->setStatuses(['processing'])
->setUids(['movies'])
->setTypes(['documentAdditionOrUpdate', 'documentDeletion'])
);
```
```java Java
TasksQuery query =
new TasksQuery()
.setStatuses(new String[] {"processing"})
.setTypes(new String[] {"documentAdditionOrUpdate", "documentDeletion"})
.setIndexUids(new String[] {"movies"});
client.getTasks(query);
```
```ruby Ruby
client.get_tasks(index_uids: ['movies'], types: ['documentAdditionOrUpdate', 'documentDeletion'], statuses: ['processing'])
```
```go Go
client.GetTasks(&meilisearch.TasksQuery{
IndexUIDS: []string{"movie"},
Types: []meilisearch.TaskType{
meilisearch.TaskTypeDocumentAdditionOrUpdate,
meilisearch.TaskTypeDocumentDeletion,
},
Statuses: []meilisearch.TaskStatus{
meilisearch.TaskStatusProcessing,
},
})
```
```csharp C#
var query = new TasksQuery { IndexUids = new List { "movies" }, Types = new List { TaskInfo.DocumentAdditionOrUpdate, TaskInfo.DocumentDeletion }, Statuses = new List { TaskInfoStatus.Processing } };
await client.GetTasksAsync(query);
```
```rust Rust
let mut query = TasksQuery::new(&client);
let tasks = query
.with_index_uids(["movies"])
.with_types(["documentAdditionOrUpdate","documentDeletion"])
.with_statuses(["processing"])
.execute()
.await
.unwrap();
```
```swift Swift
client.getTasks(params: TasksQuery(indexUids: "movies", types: ["documentAdditionOrUpdate", "documentDeletion"], statuses: ["processing"])) { result in
switch result {
case .success(let taskResult):
print(taskResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.getTasks(
params: TasksQuery(
indexUids: ['movies'],
types: ['documentAdditionOrUpdate', 'documentDeletion'],
statuses: ['processing'],
),
);
```
This code sample returns all tasks in the `movies` index that have the type `documentAdditionOrUpdate` or `documentDeletion` and have the `status` of `processing`.
**`OR` operations between different filters are not supported.** For example, you cannot view tasks which have a type of `documentAddition` **or** a status of `failed`.
# Managing the task database
Source: https://www.meilisearch.com/docs/learn/async/paginating_tasks
Meilisearch uses a task queue to handle asynchronous operations. This document describes how to navigate long task queues with filters and pagination.
By default, Meilisearch returns a list of 20 tasks for each request when you query the [get tasks endpoint](/reference/api/tasks#get-tasks). This guide shows you how to navigate the task list using query parameters.
Paginating batches with [the `/batches` route](/reference/api/batches) follows the same rules as paginating tasks.
## Configuring the number of returned tasks
Use the `limit` parameter to change the number of returned tasks:
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/tasks?limit=2&from=10
```
```javascript JS
client.tasks.getTasks({ limit: 2, from: 10 })
```
```python Python
client.get_tasks({
'limit': 2,
'from': 10
})
```
```php PHP
$taskQuery = (new TasksQuery())->setLimit(2)->setFrom(10));
$client->getTasks($taskQuery);
```
```java Java
TasksQuery query = new TasksQuery()
.setLimit(2)
.setFrom(10);
client.index("movies").getTasks(query);
```
```ruby Ruby
client.tasks(limit: 2, from: 10)
```
```go Go
client.GetTasks(&meilisearch.TasksQuery{
Limit: 2,
From: 10,
});
```
```csharp C#
ResourceResults taskResult = await client.GetTasksAsync(new TasksQuery { Limit = 2, From = 10 });
```
```rust Rust
let mut query = TasksSearchQuery::new(&client)
.with_limit(2)
.with_from(10)
.execute()
.await
.unwrap();
```
```swift Swift
client.getTasks(params: TasksQuery(limit: 2, from: 10)) { result in
switch result {
case .success(let taskResult):
print(taskResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.getTasks(params: TasksQuery(limit: 2, from: 10));
```
Meilisearch will return a batch of tasks. Each batch of returned tasks is often called a "page" of tasks, and the size of that page is determined by `limit`:
```json
{
"results": [
…
],
"total": 50,
"limit": 2,
"from": 10,
"next": 8
}
```
It is possible none of the returned tasks are the ones you are looking for. In that case, you will need to use the [get all tasks request response](/reference/api/tasks#response) to navigate the results.
## Navigating the task list with `from` and `next`
Use the `next` value included in the response to your previous query together with `from` to fetch the next set of results:
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/tasks?limit=2&from=8
```
```javascript JS
client.tasks.getTasks({ limit: 2, from: 8 })
```
```python Python
client.get_tasks({
'limit': 2,
'from': 8
})
```
```php PHP
$taskQuery = (new TasksQuery())->setLimit(2)->setFrom(8));
$client->getTasks($taskQuery);
```
```java Java
TasksQuery query = new TasksQuery()
.setLimit(2)
.setFrom(8);
client.index("movies").getTasks(query);
```
```ruby Ruby
client.tasks(limit: 2, from: 8)
```
```go Go
client.GetTasks(&meilisearch.TasksQuery{
Limit: 2,
From: 8,
});
```
```csharp C#
ResourceResults taskResult = await client.GetTasksAsync(new TasksQuery { Limit = 2, From = 8 });
```
```rust Rust
let mut query = TasksSearchQuery::new(&client)
.with_limit(2)
.from(8)
.execute()
.await
.unwrap();
```
```swift Swift
client.getTasks(params: TasksQuery(limit: 2, from: 8)) { result in
switch result {
case .success(let taskResult):
print(taskResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.getTasks(params: TasksQuery(limit: 2, from: 8));
```
This will return a new batch of tasks:
```json
{
"results": [
…
],
"total": 50,
"limit": 2,
"from": 8,
"next": 6
}
```
When the value of `next` is `null`, you have reached the final set of results.
Use `from` and `limit` together with task filtering parameters to navigate filtered task lists.
# Using task webhooks
Source: https://www.meilisearch.com/docs/learn/async/task_webhook
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](/learn/async/asynchronous_operations).
If you are using Meilisearch Cloud or need to configure multiple webhooks, use the [`/webhooks` API route](/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
Restart your Meilisearch instance and provide the webhook URL to `--task-webhook-URL`:
```sh
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`.
## Optional: configure an authorization header
Depending on your setup, you may need to provide an authorization header. Provide it to `task-webhook-authorization-header`:
```sh
meilisearch --task-webhook-url http://localhost:8000 --task-webhook-authorization-header Bearer aSampleMasterKey
```
## Test the webhook
A common asynchronous operation is adding or updating documents to an index. The following example adds a test document to our `books` index:
```sh
curl \
-X POST 'MEILISEARCH_URL/indexes/books/documents' \
-H 'Content-Type: application/json' \
--data-binary '[
{
"id": 1,
"title": "Nuestra parte de noche",
"author": "Mariana Enríquez"
}
]'
```
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
{"uid":4,"indexUid":"books","status":"succeeded","type":"documentAdditionOrUpdate","canceledBy":null,"details.receivedDocuments":1,"details.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
{"uid":4,"indexUid":"books","status":"succeeded","type":"documentAdditionOrUpdate","canceledBy":null,"details.receivedDocuments":1,"details.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,"indexUid":"books","status":"succeeded","type":"documentAdditionOrUpdate","canceledBy":null,"details.receivedDocuments":1,"details.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,"indexUid":"books","status":"succeeded","type":"documentAdditionOrUpdate","canceledBy":null,"details.receivedDocuments":1,"details.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"}
```
# Working with tasks
Source: https://www.meilisearch.com/docs/learn/async/working_with_tasks
In this tutorial, you'll use the Meilisearch API to add documents to an index, and then monitor its status.
[Many Meilisearch operations are processed asynchronously](/learn/async/asynchronous_operations) in a task. Asynchronous tasks allow you to make resource-intensive changes to your Meilisearch project without any downtime for users.
In this tutorial, you'll use the Meilisearch API to add documents to an index, and then monitor its status.
## Requirements
* a running Meilisearch project
* a command-line console
## Adding a task to the task queue
Operations that require indexing, such as adding and updating documents or changing an index's settings, will always generate a task.
Start by creating an index, then add a large number of documents to this index:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/documents'\
-H 'Content-Type: application/json' \
--data-binary @movies.json
```
```javascript JS
const movies = require('./movies.json')
client.index('movies').addDocuments(movies).then((res) => console.log(res))
```
```python Python
import json
json_file = open('movies.json', encoding='utf-8')
movies = json.load(json_file)
client.index('movies').add_documents(movies)
```
```php PHP
$moviesJson = file_get_contents('movies.json');
$movies = json_decode($moviesJson);
$client->index('movies')->addDocuments($movies);
```
```java Java
import com.meilisearch.sdk;
import org.json.JSONArray;
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("http://localhost:7700", "masterKey"));
Index index = client.index("movies");
index.addDocuments(moviesJson);
```
```ruby Ruby
require 'json'
movies_json = File.read('movies.json')
movies = JSON.parse(movies_json)
client.index('movies').add_documents(movies)
```
```go Go
import (
"encoding/json"
"os"
)
file, _ := os.ReadFile("movies.json")
var movies interface{}
json.Unmarshal([]byte(file), &movies)
client.Index("movies").AddDocuments(&movies, nil)
```
```csharp C#
// Make sure to add this using to your code
using System.IO;
var jsonDocuments = await File.ReadAllTextAsync("movies.json");
await client.Index("movies").AddDocumentsJsonAsync(jsonDocuments);
```
```rust Rust
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("http://localhost:7700", Some("masterKey"));
// 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
let path = Bundle.main.url(forResource: "movies", withExtension: "json")!
let documents: Data = try Data(contentsOf: path)
client.index("movies").addDocuments(documents: documents) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
// import 'dart:io';
// import 'dart:convert';
final json = await File('movies.json').readAsString();
await client.index('movies').addDocumentsJson(json);
```
Instead of processing your request immediately, Meilisearch will add it to a queue and return a summarized task object:
```json
{
"taskUid": 0,
"indexUid": "movies",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
The summarized task object is confirmation your request has been accepted. It also gives you information you can use to monitor the status of your request, such as the `taskUid`.
You can add documents to a new Meilisearch Cloud index using the Cloud interface. To get the `taskUid` of this task, visit the "Task" overview and look for a "Document addition or update" task associated with your newly created index.
## Monitoring task status
Meilisearch processes tasks in the order they were added to the queue. You can check the status of a task using the Meilisearch Cloud interface or the Meilisearch API.
### Monitoring task status in the Meilisearch Cloud interface
Log into your [Meilisearch Cloud](https://meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=tasks-tutorial) account and navigate to your project. Click the "Tasks" link in the project menu:
This will lead you to the task overview. Look for your request's `taskUid` in the "Uid" column:
When the task `status` changes to `succeeded`, Meilisearch has finished processing your request.
If the task `status` changes to `failed`, Meilisearch was not able to fulfill your request. Check the task object's `error` field for more information.
### Monitoring task status with the Meilisearch API
Use the `taskUid` from your request's response to check the status of a task:
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/tasks/1'
```
```javascript JS
client.tasks.getTask(1)
```
```python Python
client.get_task(1)
```
```php PHP
$client->getTask(1);
```
```java Java
client.getTask(1);
```
```ruby Ruby
client.task(1)
```
```go Go
client.GetTask(1);
```
```csharp C#
TaskInfo task = await client.GetTaskAsync(1);
```
```rust Rust
let task: Task = client
.get_task(1)
.await
.unwrap();
```
```swift Swift
client.getTask(taskUid: 1) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.getTask(1);
```
This will return the full task object:
```json
{
"uid": 4,
"indexUid" :"movie",
"status": "succeeded",
"type": "documentAdditionOrUpdate",
"canceledBy": null,
"details": {
…
},
"error": null,
"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 the task is still `enqueued` or `processing`, wait a few moments and query the database once again. You may also [set up a webhook listener](/reference/api/webhooks).
When `status` changes to `succeeded`, Meilisearch has finished processing your request.
If the task `status` changes to `failed`, Meilisearch was not able to fulfill your request. Check the task object's `error` field for more information.
## Conclusion
You have seen what happens when an API request adds a task to the task queue, and how to check the status of a that task. Consult the [task API reference](/reference/api/tasks) and the [asynchronous operations explanation](/learn/async/asynchronous_operations) for more information on how tasks work.
# Chat tooling reference
Source: https://www.meilisearch.com/docs/learn/chat/chat_tooling_reference
An exhaustive reference of special chat tools supported by Meilisearch
When creating your conversational search agent, you may be able to extend the model's capabilities with a number of tools. This page lists Meilisearch-specific tools that may improve user experience.
This is an experimental feature. Use the Meilisearch Cloud UI or the experimental features endpoint to activate it:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{
"chatCompletions": true
}'
```
## Meilisearch chat tools
For the best user experience, configure all following tools.
1. **Handle progress updates** by displaying search status to users during streaming
2. **Append conversation messages** as requested to maintain context for future requests
3. **Display source documents** to users for transparency and verification
4. **Use `call_id`** to associate progress updates with their corresponding source results
These special tools are handled internally by Meilisearch and are not forwarded to the LLM provider. They serve as a communication mechanism between Meilisearch and your application to provide enhanced user experience features.
### `_meiliSearchProgress`
This tool reports real-time progress of internal search operations. When declared, Meilisearch will call this function whenever search operations are performed in the background.
**Purpose**: Provides transparency about search operations and reduces perceived latency by showing users what's happening behind the scenes.
**Arguments**:
* `call_id`: Unique identifier to track the search operation
* `function_name`: Name of the internal function being executed (e.g., "\_meiliSearchInIndex")
* `function_parameters`: JSON-encoded string containing search parameters like `q` (query) and `index_uid`
**Example Response**:
```json
{
"function": {
"name": "_meiliSearchProgress",
"arguments": "{\"call_id\":\"89939d1f-6857-477c-8ae2-838c7a504e6a\",\"function_name\":\"_meiliSearchInIndex\",\"function_parameters\":\"{\\\"index_uid\\\":\\\"movies\\\",\\\"q\\\":\\\"search engine\\\"}\"}"
}
}
```
### `_meiliAppendConversationMessage`
Since the `/chats/{workspace}/chat/completions` endpoint is stateless, this tool helps maintain conversation context by requesting the client to append internal messages to the conversation history.
**Purpose**: Maintains conversation context for better response quality in subsequent requests by preserving tool calls and results.
**Arguments**:
* `role`: Message author role ("user" or "assistant")
* `content`: Message content (for tool results)
* `tool_calls`: Array of tool calls made by the assistant
* `tool_call_id`: ID of the tool call this message responds to
**Example Response**:
```json
{
"function": {
"name": "_meiliAppendConversationMessage",
"arguments": "{\"role\":\"assistant\",\"tool_calls\":[{\"id\":\"call_ijAdM42bixq9lAF4SiPwkq2b\",\"type\":\"function\",\"function\":{\"name\":\"_meiliSearchInIndex\",\"arguments\":\"{\\\"index_uid\\\":\\\"movies\\\",\\\"q\\\":\\\"search engine\\\"}\"}}]}"
}
}
```
### `_meiliSearchSources`
This tool provides the source documents that were used by the LLM to generate responses, enabling transparency and allowing users to verify information sources.
**Purpose**: Shows users which documents were used to generate responses, improving trust and enabling source verification.
**Arguments**:
* `call_id`: Matches the `call_id` from `_meiliSearchProgress` to associate queries with results
* `documents`: JSON object containing the source documents with only displayed attributes
**Example Response**:
```json
{
"function": {
"name": "_meiliSearchSources",
"arguments": "{\"call_id\":\"abc123\",\"documents\":[{\"id\":197302,\"title\":\"The Sacred Science\",\"overview\":\"Diabetes. Prostate cancer...\",\"genres\":[\"Documentary\",\"Adventure\",\"Drama\"]}]}"
}
}
```
### Sample OpenAI tool declaration
Include these tools in your request's `tools` array to enable enhanced functionality:
```json
{
…
"tools": [
{
"type": "function",
"function": {
"name": "_meiliSearchProgress",
"description": "Provides information about the current Meilisearch search operation",
"parameters": {
"type": "object",
"properties": {
"call_id": {
"type": "string",
"description": "The call ID to track the sources of the search"
},
"function_name": {
"type": "string",
"description": "The name of the function we are executing"
},
"function_parameters": {
"type": "string",
"description": "The parameters of the function we are executing, encoded in JSON"
}
},
"required": ["call_id", "function_name", "function_parameters"],
"additionalProperties": false
},
"strict": true
}
},
{
"type": "function",
"function": {
"name": "_meiliAppendConversationMessage",
"description": "Append a new message to the conversation based on what happened internally",
"parameters": {
"type": "object",
"properties": {
"role": {
"type": "string",
"description": "The role of the messages author, either `role` or `assistant`"
},
"content": {
"type": "string",
"description": "The contents of the `assistant` or `tool` message. Required unless `tool_calls` is specified."
},
"tool_calls": {
"type": ["array", "null"],
"description": "The tool calls generated by the model, such as function calls",
"items": {
"type": "object",
"properties": {
"function": {
"type": "object",
"description": "The function that the model called",
"properties": {
"name": {
"type": "string",
"description": "The name of the function to call"
},
"arguments": {
"type": "string",
"description": "The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function."
}
}
},
"id": {
"type": "string",
"description": "The ID of the tool call"
},
"type": {
"type": "string",
"description": "The type of the tool. Currently, only function is supported"
}
}
}
},
"tool_call_id": {
"type": ["string", "null"],
"description": "Tool call that this message is responding to"
}
},
"required": ["role", "content", "tool_calls", "tool_call_id"],
"additionalProperties": false
},
"strict": true
}
},
{
"type": "function",
"function": {
"name": "_meiliSearchSources",
"description": "Provides sources of the search",
"parameters": {
"type": "object",
"properties": {
"call_id": {
"type": "string",
"description": "The call ID to track the original search associated to those sources"
},
"documents": {
"type": "object",
"description": "The documents associated with the search (call_id). Only the displayed attributes of the documents are returned"
}
},
"required": ["call_id", "documents"],
"additionalProperties": false
},
"strict": true
}
}
]
}
```
# Conversational search
Source: https://www.meilisearch.com/docs/learn/chat/conversational_search
Learn how to implement AI-powered conversational search using Meilisearch's chat feature
Meilisearch's chat completions feature enables AI-powered conversational search, allowing users to ask questions in natural language and receive direct answers based on your indexed content. This feature transforms the traditional search experience into an interactive dialogue.
This is an experimental feature. Use the Meilisearch Cloud UI or the experimental features endpoint to activate it:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{
"chatCompletions": true
}'
```
## What is conversational search?
Conversational search interfaces allow users to:
* Ask questions in natural language instead of using keywords
* Receive direct answers rather than just document links
* Maintain context across multiple questions
* Get responses grounded in your actual content
This approach bridges the gap between traditional search and modern AI experiences, making information more accessible and intuitive to find.
## How chat completions differs from traditional search
### Traditional search workflow
1. User enters keywords
2. Meilisearch returns matching documents
3. User reviews results to find answers
### Conversational search workflow
1. User asks a question in natural language
2. Meilisearch retrieves relevant documents
3. AI generates a direct answer based on those documents
4. User can ask follow-up questions
## When to use chat completions vs traditional search
### Use conversational search when:
* Users need direct answers to specific questions
* Content is informational (documentation, knowledge bases, FAQs)
* Users benefit from follow-up questions
* Natural language interaction improves user experience
### Use traditional search when:
* Users need to browse multiple options
* Results require comparison (e-commerce products, listings)
* Exact matching is critical
* Response time is paramount
## Use chat completions to implement RAG pipelines
The chat completions feature implements a complete Retrieval Augmented Generation (RAG) pipeline in a single API endpoint. Meilisearch's chat completions consolidates RAG creation into one streamlined process:
1. **Query understanding**: automatically transforms questions into search parameters
2. **Hybrid retrieval**: combines keyword and semantic search for better relevancy
3. **Answer generation**: uses your chosen LLM to generate responses
4. **Context management**: maintains conversation history by constantly pushing the full conversation to the dedicated tool
### Alternative: MCP integration
When integrating Meilisearch with AI assistants and automation tools, consider using [Meilisearch's Model Context Protocol (MCP) server](/guides/ai/mcp). MCP enables standardized tool integration across various AI platforms and applications.
## Architecture overview
Chat completions operate through workspaces, which are isolated configurations for different use cases. Each workspace can:
* Use different LLM sources (openAi, azureOpenAi, mistral, gemini, vLlm)
* Apply custom prompts
* Access specific indexes based on API keys
* Maintain separate conversation contexts
### Key components
1. **Chat endpoint**: `/chats/{workspace}/chat/completions`
* OpenAI-compatible interface
* Supports streaming responses
* Handles tool calling for index searches
2. **Workspace settings**: `/chats/{workspace}/settings`
* Configure LLM provider and model
* Set system prompts
* Manage API credentials
3. **Index integration**:
* Automatically searches relevant indexes
* Uses existing Meilisearch search capabilities
* Respects API key permissions
## Security considerations
The chat completions feature integrates with Meilisearch's existing security model:
* **API key permissions**: chat only accesses indexes visible to the provided API key
* **Tenant tokens**: support for multi-tenant applications
* **LLM credentials**: stored securely in workspace settings
* **Content isolation**: responses based only on indexed content
## Next steps
* [Get started with chat completions implementation](/learn/chat/getting_started_with_chat)
* [Explore the chat completions API reference](/reference/api/chats)
# Getting started with conversational search
Source: https://www.meilisearch.com/docs/learn/chat/getting_started_with_chat
Learn how to implement AI-powered conversational search in your application
This guide walks you through implementing Meilisearch's chat completions feature to create conversational search experiences in your application.
## Prerequisites
Before starting, ensure you have:
* A [secure](/learn/security/basic_security) Meilisearch >= v1.15.1 project
* An API key from an LLM provider
* At least one index with searchable content
## Enable the chat completions feature
First, enable the chat completions experimental feature:
```bash
curl \
-X PATCH 'http://localhost:7700/experimental-features/' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"chatCompletions": true
}'
```
## Find your chat API key
When Meilisearch runs with a master key on an instance created after v1.15.1, it automatically generates a "Default Chat API Key" with `chatCompletions` and `search` permissions on all indexes. Check if you have the key using:
```bash
curl http://localhost:7700/keys \
-H "Authorization: Bearer MEILISEARCH_KEY"
```
Look for the key with the description "Default Chat API Key" Use this key when querying the `/chats` endpoint.
### Troubleshooting: Missing default chat API key
If your instance does not have a Default Chat API Key, create one manually:
```bash
curl \
-X POST 'http://localhost:7700/keys' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"name": "Chat API Key",
"description": "API key for chat completions",
"actions": ["search", "chatCompletions"],
"indexes": ["*"],
"expiresAt": null
}'
```
## Configure your indexes for chat
Each index that you want to be searchable through chat needs specific configuration:
```bash
curl \
-X PATCH 'http://localhost:7700/indexes/movies/settings' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"chat": {
"description": "A comprehensive movie database containing titles, descriptions, genres, and release dates to help users find movies",
"documentTemplate": "{% for field in fields %}{% if field.is_searchable and field.value != nil %}{{ field.name }}: {{ field.value }}\n{% endif %}{% endfor %}",
"documentTemplateMaxBytes": 400,
"searchParameters": {}
}
}'
```
The `description` field helps the LLM understand what data is in the index, improving search relevance.
## Configure a chat completions workspace
Create a workspace with your LLM provider settings. Here are examples for different providers:
```bash openAi
curl \
-X PATCH 'http://localhost:7700/chats/my-assistant/settings' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"source": "openAi",
"apiKey": "sk-abc...",
"baseUrl": "https://api.openai.com/v1",
"prompts": {
"system": "You are a helpful assistant. Answer questions based only on the provided context."
}
}'
```
```bash azureOpenAi
curl \
-X PATCH 'http://localhost:7700/chats/my-assistant/settings' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"source": "azureOpenAi",
"apiKey": "your-azure-key",
"baseUrl": "https://your-resource.openai.azure.com",
"prompts": {
"system": "You are a helpful assistant. Answer questions based only on the provided context."
}
}'
```
```bash mistral
curl \
-X PATCH 'http://localhost:7700/chats/my-assistant/settings' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"source": "mistral",
"apiKey": "your-mistral-key",
"prompts": {
"system": "You are a helpful assistant. Answer questions based only on the provided context."
}
}'
```
```bash gemini
curl \
-X PATCH 'http://localhost:7700/chats/my-assistant/settings' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"source": "gemini",
"apiKey": "your-gemini-key",
"prompts": {
"system": "You are a helpful assistant. Answer questions based only on the provided context."
}
}'
```
```bash vLlm
curl \
-X PATCH 'http://localhost:7700/chats/my-assistant/settings' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"source": "vLlm",
"baseUrl": "http://localhost:8000",
"prompts": {
"system": "You are a helpful assistant. Answer questions based only on the provided context."
}
}'
```
## Send your first chat completions request
Now you can start a conversation. Note the `-N` flag for handling streaming responses:
```bash
curl -N \
-X POST 'http://localhost:7700/chats/my-assistant/chat/completions' \
-H 'Authorization: Bearer ' \
-H 'Content-Type: application/json' \
--data-binary '{
"model": "gpt-3.5-turbo",
"messages": [
{
"role": "user",
"content": "What movies do you have about space exploration?"
}
],
"stream": true,
"tools": [
{
"type": "function",
"function": {
"name": "_meiliSearchProgress",
"description": "Reports real-time search progress to the user"
}
},
{
"type": "function",
"function": {
"name": "_meiliSearchSources",
"description": "Provides sources and references for the information"
}
}
]
}'
```
Take particular note of the `tools` array. These settings are optional, but greatly improve user experience:
* **`_meiliSearchProgress`**: shows users what searches are being performed
* **`_meiliSearchSources`**: displays the actual documents used to generate responses
## Build a chat interface using the OpenAI SDK
Since Meilisearch's chat endpoint is OpenAI-compatible, you can use the official OpenAI SDK:
```javascript JavaScript
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'http://localhost:7700/chats/my-assistant',
apiKey: 'YOUR_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 || '');
}
```
```python Python
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:7700/chats/my-assistant",
api_key="YOUR_CHAT_API_KEY"
)
stream = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "What is Meilisearch?"}],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="")
```
```typescript TypeScript
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'http://localhost:7700/chats/my-assistant',
apiKey: 'YOUR_CHAT_API_KEY',
});
const stream = await client.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: 'What is Meilisearch?' }],
stream: true,
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
process.stdout.write(content);
}
```
### Error handling
When using the OpenAI SDK with Meilisearch's chat completions endpoint, errors from the streamed responses are natively handled by OpenAI. This means you can use the SDK's built-in error handling mechanisms without additional configuration:
```javascript JavaScript
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'http://localhost:7700/chats/my-assistant',
apiKey: 'MEILISEARCH_KEY',
});
try {
const stream = await client.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: 'What is Meilisearch?' }],
stream: true,
});
for await (const chunk of stream) {
console.log(chunk.choices[0]?.delta?.content || '');
}
} catch (error) {
// OpenAI SDK automatically handles streaming errors
console.error('Chat completion error:', error);
}
```
```python Python
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:7700/chats/my-assistant",
api_key="MEILISEARCH_KEY"
)
try:
stream = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "What is Meilisearch?"}],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="")
except Exception as error:
# OpenAI SDK automatically handles streaming errors
print(f"Chat completion error: {error}")
```
## Troubleshooting
### Common issues and solutions
#### Empty reply from server (curl error 52)
**Causes:**
* Meilisearch not started with a master key
* Experimental features not enabled
* Missing authentication in requests
**Solution:**
1. Restart Meilisearch with a master key: `meilisearch --master-key yourKey`
2. Enable experimental features (see setup instructions above)
3. Include Authorization header in all requests
#### "Invalid API key" error
**Cause:** Using the wrong type of API key
**Solution:**
* Use either the master key or the "Default Chat API Key"
* Don't use search or admin API keys for chat endpoints
* Find your chat key: `curl http://localhost:7700/keys -H "Authorization: Bearer MEILISEARCH_KEY"`
#### "Socket connection closed unexpectedly"
**Cause:** Usually means the OpenAI API key is missing or invalid in workspace settings
**Solution:**
1. Check workspace configuration:
```bash
curl http://localhost:7700/chats/my-assistant/settings \
-H "Authorization: Bearer MEILISEARCH_KEY"
```
2. Update with valid API key:
```bash
curl -X PATCH http://localhost:7700/chats/my-assistant/settings \
-H "Authorization: Bearer MEILISEARCH_KEY" \
-H "Content-Type: application/json" \
-d '{"apiKey": "your-valid-api-key"}'
```
#### Chat not searching the database
**Cause:** Missing Meilisearch tools in the request
**Solution:**
* Include `_meiliSearchProgress` and `_meiliSearchSources` tools in your request
* Ensure indexes have proper chat descriptions configured
#### "stream: false is not supported" error
**Cause:** Trying to use non-streaming responses
**Solution:**
* Always set `"stream": true` in your requests
* Non-streaming responses are not yet supported
## Next steps
* Explore [advanced chat API features](/reference/api/chats)
* Learn about [conversational search concepts](/learn/chat/conversational_search)
* Review [security best practices](/learn/security/basic_security)
# Configuring index settings
Source: https://www.meilisearch.com/docs/learn/configuration/configuring_index_settings
This tutorial shows how to check and change an index setting using the Meilisearch Cloud interface.
This tutorial will show you how to check and change an index setting using the [Meilisearch Cloud](https://cloud.meilisearch.com/projects/) interface.
## Requirements
* an active [Meilisearch Cloud](https://cloud.meilisearch.com/projects/) account
* a Meilisearch Cloud project with at least one index
## Accessing a project's index settings
Log into your Meilisearch account and navigate to your project. Then, click on "Indexes":
Find the index you want to configure and click on its "Settings" button:
## Checking a setting's current value
Using the menu on the left-hand side, click on "Attributes":
The first setting is "Searchable attributes" and lists all attributes in your dataset's documents:
Clicking on other settings will show you similar interfaces that allow visualizing and editing all Meilisearch index settings.
## Updating a setting
All documents include a primary key attribute. In most cases, this attribute does not contain information relevant for searches, so you can improve your application's search by explicitly removing it from the searchable attributes list.
Find your primary key, then click on the bin icon:
Meilisearch will display a pop-up window asking you to confirm you want to remove the attribute from the searchable attributes list. Click on "Yes, remove attribute":
Most updates to an index's settings will cause Meilisearch to re-index all its data. Wait a few moments until this operation is complete. You are not allowed to update any index settings during this time.
Once Meilisearch finishes indexing, the primary key will no longer appear in the searchable attributes list:
If you deleted the wrong attribute, click on "Add attributes" to add it back to the list. You may also click on "Reset to default", which will bring back the searchable list to its original state when you first added your first document to this index:
## Conclusion
You have used the Meilisearch Cloud interface to check the value of an index setting. This revealed an opportunity to improve your project's performance, so you updated this index setting to make your application better and more responsive.
This tutorial used the "Searchable attributes" setting, but the procedure is the same no matter which index setting you are editing.
## What's next
If you prefer to access the settings API directly through your console, you can also [configure index settings using the Meilisearch Cloud API](/learn/configuration/configuring_index_settings_api).
For a comprehensive reference of all index settings, consult the [settings API reference](/reference/api/settings).
# Configuring index settings with the Meilisearch API
Source: https://www.meilisearch.com/docs/learn/configuration/configuring_index_settings_api
This tutorial shows how to check and change an index setting using the Meilisearch API.
This tutorial shows how to check and change an index setting using one of the setting subroutes of the Meilisearch API.
If you are Meilisearch Cloud user, you may also [configure index settings using the Meilisearch Cloud interface](/learn/configuration/configuring_index_settings).
## Requirements
* a new [Meilisearch Cloud](https://cloud.meilisearch.com/projects/) project or a self-hosted Meilisearch instance with at least one index
* a command-line terminal with `curl` installed
## Getting the value of a single index setting
Start by checking the value of the searchable attributes index setting.
Use the `GET` endpoint of the `/settings/searchable-attributes` subroute, replacing `INDEX_NAME` with your index:
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/searchable-attributes'
```
```rust Rust
let searchable_attributes: Vec = index
.get_searchable_attributes()
.await
.unwrap();
```
Depending on your setup, you might also need to replace `localhost:7700` with the appropriate address and port.
You should receive a response immediately:
```json
[
"*"
]
```
If this is a new index, you should see the default value, \["\*"]. This indicates Meilisearch looks through all document attributes when searching.
## Updating an index setting
All documents include a primary key attribute. In most cases, this attribute does not contain any relevant data, so you can improve your application search experience by explicitly removing it from your searchable attributes list.
Use the `PUT` endpoint of the `/settings/searchable-attributes` subroute, replacing `INDEX_NAME` with your index and the sample attributes `"title"` and `"overview"` with attributes present in your dataset:
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/searchable-attributes' \
-H 'Content-Type: application/json' \
--data-binary '[
"title",
"overview"
]'
```
```rust Rust
let task = index
.set_searchable_attributes(["title", "overview"])
.await
.unwrap();
```
This time, Meilisearch will not process your request immediately. Instead, you will receive a summarized task object while the search engine works on updating your index setting as soon as it has enough resources:
```json
{
"taskUid": 1,
"indexUid": "INDEX_NAME",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
Processing the index setting change might take some time, depending on how many documents you have in your index. Wait a few seconds and use the task object's `taskUid` to monitor the status of your request:
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/tasks/TASK_UID'
```
```rust Rust
let task_status = index.get_task(&task).await.unwrap();
```
Meilisearch will respond with a task object:
```json
{
"uid": 1,
"indexUid": "INDEX_NAME",
"status": "succeeded",
"type": "settingsUpdate",
…
}
```
If `status` is `enqueued` or `processed`, wait a few more moments and check the task status again. If `status` is `failed`, make sure you have used a valid index and attributes, then try again.
If task `status` is `succeeded`, you successfully updated your index's searchable attributes. Use the subroute to check the new setting's value:
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/searchable-attributes'
```
```rust Rust
let searchable_attributes: Vec = index
.get_searchable_attributes()
.await
.unwrap();
```
Meilisearch should return an array with the new values:
```json
[
"title",
"overview"
]
```
## Conclusion
You have used the Meilisearch API to check the value of an index setting. This revealed an opportunity to improve your project's performance, so you updated this index setting to make your application better and more responsive.
This tutorial used the searchable attributes setting, but the procedure is the same no matter which index setting you are editing.
For a comprehensive reference of all index settings, consult the [settings API reference](/reference/api/settings).
# Exporting and importing dumps
Source: https://www.meilisearch.com/docs/learn/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](/learn/data_backup/snapshots_vs_dumps#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](/learn/update_and_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](/reference/api/dump#create-a-dump):
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/dumps'
```
```javascript JS
client.createDump()
```
```python Python
client.create_dump()
```
```php PHP
$client->createDump();
```
```java Java
client.createDump();
```
```ruby Ruby
client.create_dump
```
```go Go
resp, err := client.CreateDump()
```
```csharp C#
await client.CreateDumpAsync();
```
```rust Rust
client
.create_dump()
.await
.unwrap();
```
```swift Swift
client.createDump { result in
switch result {
case .success(let dumpStatus):
print(dumpStatus)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.createDump();
```
This will return a [summarized task object](/learn/async/asynchronous_operations#summarized-task-objects) that you can use to check the status of your dump.
```json
{
"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
curl \
-X GET 'MEILISEARCH_URL/tasks/1'
```
```javascript JS
client.tasks.getTask(1)
```
```python Python
client.get_task(1)
```
```php PHP
$client->getTask(1);
```
```java Java
client.getTask(1);
```
```ruby Ruby
client.task(1)
```
```go Go
client.GetTask(1);
```
```csharp C#
TaskInfo task = await client.GetTaskAsync(1);
```
```rust Rust
let task: Task = client
.get_task(1)
.await
.unwrap();
```
```swift Swift
client.getTask(taskUid: 1) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.getTask(1);
```
This should return an object with detailed information about the dump operation:
```json
{
"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](/learn/self_hosted/configure_meilisearch_at_launch#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
### Importing a dump in Meilisearch Cloud
You can import a dump into Meilisearch when creating a new project, below the plan selector:
### Importing a dump in self-hosted instances
Import a dump by launching a Meilisearch instance with the [`--import-dump` configuration option](/learn/self_hosted/configure_meilisearch_at_launch#import-dump):
```bash
./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.
# Exporting and using Snapshots
Source: https://www.meilisearch.com/docs/learn/data_backup/snapshots
Snapshots are exact copies of Meilisearch databases. They are often useful for periodical backups.
A [snapshot](/learn/data_backup/snapshots_vs_dumps#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](/learn/self_hosted/configure_meilisearch_at_launch#schedule-snapshot-creation) to create snapshots at regular time intervals:
```bash
meilisearch --schedule-snapshot
```
The first snapshot is created on launch. You will find it in the [snapshot directory](/learn/self_hosted/configure_meilisearch_at_launch#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
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](/reference/api/snapshots).
## Starting from a snapshot
To import snapshot data into your instance, launch Meilisearch using `--import-snapshot`:
```bash
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`](/learn/self_hosted/configure_meilisearch_at_launch#ignore-dump-if-db-exists). This will cause Meilisearch to launch with the existing database and ignore the dump without throwing an error.
# Snapshots and dumps
Source: https://www.meilisearch.com/docs/learn/data_backup/snapshots_vs_dumps
Meilisearch offers two types of backups: snapshots and dumps. Snapshots are mainly intended as a safeguard, while dumps are useful when migrating Meilisearch.
This article explains Meilisearch's two backup methods: snapshots and dumps.
## Snapshots
A snapshot is an exact copy of the Meilisearch database, located by default in `./data.ms`. [Use snapshots for quick and efficient backups of your instance](/learn/data_backup/snapshots).
The documents in a snapshot are already indexed and ready to go, greatly increasing import speed. However, snapshots are not compatible between different versions of Meilisearch. Snapshots are also significantly bigger than dumps.
In short, snapshots are a safeguard: if something goes wrong in an instance, you're able to recover and relaunch your database quickly. You can also schedule periodic snapshot creation.
## Dumps
A dump isn't an exact copy of your database like a snapshot. Instead, it is closer to a blueprint which Meilisearch can later use to recreate a whole instance from scratch.
Importing a dump requires Meilisearch to re-index all documents. This process uses a significant amount of time and memory proportional to the size of the database. Compared to the snapshots, importing a dump is a slow and inefficient operation.
At the same time, dumps are not bound to a specific Meilisearch version. This means dumps are ideal for migrating your data when you upgrade Meilisearch.
Use dumps to transfer data from an old Meilisearch version into a more recent release. Do not transfer data from a new release into a legacy Meilisearch version.
For example, you can import a dump from Meilisearch v1.2 into v1.6 without any problems. Importing a dump generated in v1.7 into a v1.2 instance, however, can lead to unexpected behavior.
## Snapshots VS dumps
Both snapshots and dumps are data backups, but they serve different purposes.
Snapshots are highly efficient, but not portable between different versions of Meilisearch. **Use snapshots for periodic data backups.**
Dumps are portable between different Meilisearch versions, but not very efficient. **Use dumps when updating to a new Meilisearch release.**
# Concatenated and split queries
Source: https://www.meilisearch.com/docs/learn/engine/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/learn/engine/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.8.3/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](/reference/api/settings#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](/learn/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
[
{
"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](/learn/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](/learn/filtering_and_sorting/filter_search_results). The `>`, `>=`, `<`, `<=`, and `TO` relational operators apply only to numerical values.
## 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
[
[
"Bruce Willis",
"Vin Diesel"
],
"Kung Fu Panda"
]
```
Will be processed as if all elements were arranged at the same level:
```json
"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](/learn/engine/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
{
"id": 0,
"patient_name": {
"forename": "Imogen",
"surname": "Temult"
}
}
```
During indexing, Meilisearch uses dot notation to eliminate nested fields:
```json
{
"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.
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
{
"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
{
"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
{
"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
…
"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
[
{
"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
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
[
{
"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"
]
}
]
```
## 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.
# Prefix search
Source: https://www.meilisearch.com/docs/learn/engine/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](/learn/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](/learn/relevancy/typo_tolerance_settings).
We also [apply splitting and concatenating on search queries](/learn/engine/concat).
# Storage
Source: https://www.meilisearch.com/docs/learn/engine/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](/learn/self_hosted/configure_meilisearch_at_launch#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 the best performance, it is recommended to provide the same amount of RAM as the size the database takes on disk, so all the data structures can fit in memory.
### 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. In general, you should have the same amount of RAM as the space taken on disk by Meilisearch for optimal performance.
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—we're just the only ones saying it out loud.
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.
# Filter expression reference
Source: https://www.meilisearch.com/docs/learn/filtering_and_sorting/filter_expression_reference
The `filter` search parameter expects a filter expression. Filter expressions are made of attributes, values, and several operators.
export const NoticeTag = ({label}) =>
{label}
;
The `filter` search parameter expects a filter expression. Filter expressions are made of attributes, values, and several operators.
`filter` expects a **filter expression** containing one or more **conditions**. A filter expression can be written as a string, array, or mix of both.
## Data types
Filters accept numeric and string values. Empty fields or fields containing an empty array will be ignored.
Filters do not work with [`NaN`](https://en.wikipedia.org/wiki/NaN) and infinite values such as `inf` and `-inf` as they are [not supported by JSON](https://en.wikipedia.org/wiki/JSON#Data_types). It is possible to filter infinite and `NaN` values if you parse them as strings, except when handling [`_geo` fields](/learn/filtering_and_sorting/geosearch#preparing-documents-for-location-based-search).
For best results, enforce homogeneous typing across fields, especially when dealing with large numbers. Meilisearch does not enforce a specific schema when indexing data, but the filtering engine may coerce the type of `value`. This can lead to undefined behavior, such as when big floating-point numbers are coerced into integers.
## Conditions
Conditions are a filter's basic building blocks. They are written in the `attribute OPERATOR value` format, where:
* `attribute` is the attribute of the field you want to filter on
* `OPERATOR` can be `=`, `!=`, `>`, `>=`, `<`, `<=`, `TO`, `EXISTS`, `IN`, `NOT`, `AND`, or `OR`
* `value` is the value the `OPERATOR` should look for in the `attribute`
### Examples
A basic condition requesting movies whose `genres` attribute is equal to `horror`:
```
genres = horror
```
String values containing whitespace must be enclosed in single or double quotes:
```
director = 'Jordan Peele'
director = "Tim Burton"
```
## Filter operators
### Equality (`=`)
The equality operator (`=`) returns all documents containing a specific value for a given attribute:
```
genres = action
```
When operating on strings, `=` is case-insensitive.
The equality operator does not return any results for `null` and empty arrays.
### Inequality (`!=`)
The inequality operator (`!=`) returns all documents not selected by the equality operator. When operating on strings, `!=` is case-insensitive.
The following expression returns all movies without the `action` genre:
```
genres != action
```
### Comparison (`>`, `<`, `>=`, `<=`)
The comparison operators (`>`, `<`, `>=`, `<=`) select documents satisfying a comparison. Comparison operators apply to both numerical and string values.
The expression below returns all documents with a user rating above 85:
```
rating.users > 85
```
String comparisons resolve in lexicographic order: symbols followed by numbers followed by letters in alphabetic order. The expression below returns all documents released after the first day of 2004:
```
release_date > 2004-01-01
```
### `TO`
`TO` is equivalent to `>= AND <=`. The following expression returns all documents with a rating of 80 or above but below 90:
```
rating.users 80 TO 89
```
### `EXISTS`
The `EXISTS` operator checks for the existence of a field. Fields with empty or `null` values count as existing.
The following expression returns all documents containing the `release_date` field:
```
release_date EXISTS
```
The negated form of the above expression can be written in two equivalent ways:
```
release_date NOT EXISTS
NOT release_date EXISTS
```
#### Vector filters
When using AI-powered search, you may also use `EXISTS` to filter documents containing vector data:
* `_vectors EXISTS`: matches all documents with an embedding
* `_vectors.{embedder_name} EXISTS`: matches all documents with an embedding for the given embedder
* `_vectors.{embedder_name}.userProvided EXISTS`: matches all documents with a user-provided embedding on the given embedder
* `_vectors.{embedder_name}.documentTemplate EXISTS`: matches all documents with an embedding generated from a document template. Excludes user-provided embeddings
* `_vectors.{embedder_name}.regenerate EXISTS`: matches all documents with an embedding scheduled for regeneration
* `_vectors.{embedder_name}.fragments.{fragment_name} EXISTS`: matches all documents with an embedding generated from the given multimodal fragment. Excludes user-provided embeddings
`_vectors` is only compatible with the `EXISTS` operator.
### `IS EMPTY`
The `IS EMPTY` operator selects documents in which the specified attribute exists but contains empty values. The following expression only returns documents with an empty `overview` field:
```
overview IS EMPTY
```
`IS EMPTY` matches the following JSON values:
* `""`
* `[]`
* `{}`
Meilisearch does not treat `null` values as empty. To match `null` fields, use the [`IS NULL`](#is-null) operator.
Use `NOT` to build the negated form of `IS EMPTY`:
```
overview IS NOT EMPTY
NOT overview IS EMPTY
```
### `IS NULL`
The `IS NULL` operator selects documents in which the specified attribute exists but contains a `null` value. The following expression only returns documents with a `null` `overview` field:
```
overview IS NULL
```
Use `NOT` to build the negated form of `IS NULL`:
```
overview IS NOT NULL
NOT overview IS NULL
```
### `IN`
`IN` combines equality operators by taking an array of comma-separated values delimited by square brackets. It selects all documents whose chosen field contains at least one of the specified values.
The following expression returns all documents whose `genres` includes either `horror`, `comedy`, or both:
```
genres IN [horror, comedy]
genres = horror OR genres = comedy
```
The negated form of the above expression can be written as:
```
genres NOT IN [horror, comedy]
NOT genres IN [horror, comedy]
```
### `CONTAINS`
`CONTAINS` filters results containing partial matches to the specified string pattern, similar to a [SQL `LIKE`](https://dev.mysql.com/doc/refman/8.4/en/string-comparison-functions.html#operator_like).
The following expression returns all dairy products whose names contain `"kef"`:
```
dairy_products.name CONTAINS kef
```
The negated form of the above expression can be written as:
```
dairy_products.name NOT CONTAINS kef
NOT dairy_product.name CONTAINS kef
```
This is an experimental feature. Use the experimental features endpoint to activate it:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{
"containsFilter": true
}'
```
### `STARTS WITH`
`STARTS WITH` filters results whose values start with the specified string pattern.
The following expression returns all dairy products whose name start with `"kef"`:
```
dairy_products.name STARTS WITH kef
```
The negated form of the above expression can be written as:
```
dairy_products.name NOT STARTS WITH kef
NOT dairy_product.name STARTS WITH kef
```
### `NOT`
The negation operator (`NOT`) selects all documents that do not satisfy a condition. It has higher precedence than `AND` and `OR`.
The following expression will return all documents whose `genres` does not contain `horror` and documents with a missing `genres` field:
```
NOT genres = horror
```
## Filter expressions
You can build filter expressions by grouping basic conditions using `AND` and `OR`. Filter expressions can be written as strings, arrays, or a mix of both.
### Filter expression grouping operators
#### `AND`
`AND` connects two conditions and only returns documents that satisfy both of them. `AND` has higher precedence than `OR`.
The following expression returns all documents matching both conditions:
```
genres = horror AND director = 'Jordan Peele'
```
#### `OR`
`OR` connects two conditions and returns results that satisfy at least one of them.
The following expression returns documents matching either condition:
```
genres = horror OR genres = comedy
```
### Creating filter expressions with string operators and parentheses
Meilisearch reads string expressions from left to right. You can use parentheses to ensure expressions are correctly parsed.
For instance, if you want your results to only include `comedy` and `horror` documents released after March 1995, the parentheses in the following query are mandatory:
```
(genres = horror OR genres = comedy) AND release_date > 795484800
```
Failing to add these parentheses will cause the same query to be parsed as:
```
genres = horror OR (genres = comedy AND release_date > 795484800)
```
Translated into English, the above expression will only return comedies released after March 1995 or horror movies regardless of their `release_date`.
When creating an expression with a field name or value identical to a filter operator such as `AND` or `NOT`, you must wrap it in quotation marks: `title = "NOT" OR title = "AND"`.
### Creating filter expressions with arrays
Array expressions establish logical connectives by nesting arrays of strings. **Array filters can have a maximum depth of two.** Expressions with three or more levels of nesting will throw an error.
Outer array elements are connected by an `AND` operator. The following expression returns `horror` movies directed by `Jordan Peele`:
```
["genres = horror", "director = 'Jordan Peele'"]
```
Inner array elements are connected by an `OR` operator. The following expression returns either `horror` or `comedy` films:
```
[["genres = horror", "genres = comedy"]]
```
Inner and outer arrays can be freely combined. The following expression returns both `horror` and `comedy` movies directed by `Jordan Peele`:
```
[["genres = horror", "genres = comedy"], "director = 'Jordan Peele'"]
```
### Combining arrays and string operators
You can also create filter expressions that use both array and string syntax.
The following filter is written as a string and only returns movies not directed by `Jordan Peele` that belong to the `comedy` or `horror` genres:
```
"(genres = comedy OR genres = horror) AND director != 'Jordan Peele'"
```
You can write the same filter mixing arrays and strings:
```
[["genres = comedy", "genres = horror"], "NOT director = 'Jordan Peele'"]
```
# Filter search results
Source: https://www.meilisearch.com/docs/learn/filtering_and_sorting/filter_search_results
In this guide you will see how to configure and use Meilisearch filters in a hypothetical movie database.
In this guide you will see how to configure and use Meilisearch filters in a hypothetical movie database.
## Configure index settings
Suppose you have a collection of movies called `movie_ratings` containing the following fields:
```json
[
{
"id": 458723,
"title": "Us",
"director": "Jordan Peele",
"release_date": 1552521600,
"genres": [
"Thriller",
"Horror",
"Mystery"
],
"rating": {
"critics": 86,
"users": 73
},
},
…
]
```
If you want to filter results based on an attribute, you must first add it to the `filterableAttributes` list:
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/movie_ratings/settings/filterable-attributes' \
-H 'Content-Type: application/json' \
--data-binary '[
"genres",
"director",
"release_date",
"ratings"
]'
```
```javascript JS
client.index('movies')
.updateFilterableAttributes([
'director',
'genres'
])
```
```python Python
client.index('movies').update_filterable_attributes([
'director',
'genres',
])
```
```php PHP
$client->index('movies')->updateFilterableAttributes(['director', 'genres']);
```
```java Java
client.index("movies").updateFilterableAttributesSettings(new String[]
{
"genres",
"director"
});
```
```ruby Ruby
client.index('movies').update_filterable_attributes([
'director',
'genres'
])
```
```go Go
resp, err := client.Index("movies").UpdateFilterableAttributes(&[]interface{}{
"director",
"genres",
})
```
```csharp C#
await client.Index("movies").UpdateFilterableAttributesAsync(new [] { "director", "genres" });
```
```rust Rust
let task: TaskInfo = client
.index("movies")
.set_filterable_attributes(["director", "genres"])
.await
.unwrap();
```
```swift Swift
client.index("movies").updateFilterableAttributes(["genre", "director"]) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').updateFilterableAttributes([
'director',
'genres',
]);
```
**This step is mandatory and cannot be done at search time**. Updating `filterableAttributes` requires Meilisearch to re-index all your data, which will take an amount of time proportionate to your dataset size and complexity.
By default, `filterableAttributes` is empty. Filters do not work without first explicitly adding attributes to the `filterableAttributes` list.
## Use `filter` when searching
After updating the [`filterableAttributes` index setting](/reference/api/settings#filterable-attributes), you can use `filter` to fine-tune your search results.
`filter` is a search parameter you may use at search time. `filter` accepts [filter expressions](/learn/filtering_and_sorting/filter_expression_reference) built using any attributes present in the `filterableAttributes` list.
The following code sample returns `Avengers` movies released after 18 March 1995:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movie_ratings/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "Avengers",
"filter": "release_date > 795484800"
}'
```
```javascript JS
client.index('movie_ratings').search('Avengers', {
filter: 'release_date > 795484800'
})
```
```python Python
client.index('movie_ratings').search('Avengers', {
'filter': 'release_date > 795484800'
})
```
```php PHP
$client->index('movie_ratings')->search('Avengers', [
'filter' => 'release_date > 795484800'
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("Avengers").filter(new String[] {"release_date > \"795484800\""}).build();
client.index("movie_ratings").search(searchRequest);
```
```ruby Ruby
client.index('movie_ratings').search('Avengers', { filter: 'release_date > 795484800' })
```
```go Go
resp, err := client.Index("movie_ratings").Search("Avengers", &meilisearch.SearchRequest{
Filter: "release_date > \"795484800\"",
})
```
```csharp C#
SearchQuery filters = new SearchQuery() { Filter = "release_date > \"795484800\"" };
var movies = await client.Index("movie_ratings").SearchAsync("Avengers", filters);
```
```rust Rust
let results: SearchResults = client
.index("movie_ratings")
.search()
.with_query("Avengers")
.with_filter("release_date > 795484800")
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "Avengers",
filter: "release_date > 795484800"
)
client.index("movie_ratings").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movie_ratings').search(
'Avengers',
SearchQuery(
filterExpression: Meili.gt(
Meili.attr('release_date'),
DateTime.utc(1995, 3, 18).toMeiliValue(),
),
),
);
```
Use dot notation to filter results based on a document's [nested fields](/learn/engine/datatypes). The following query only returns thrillers with good user reviews:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movie_ratings/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "thriller",
"filter": "rating.users >= 90"
}'
```
```javascript JS
client.index('movie_ratings').search('thriller', {
filter: 'rating.users >= 90'
})
```
```python Python
client.index('movie_ratings').search('thriller', {
'filter': 'rating.users >= 90'
})
```
```php PHP
$client->index('movie_ratings')->search('thriller', [
'filter' => 'rating.users >= 90'
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("thriller").filter(new String[] {"rating.users >= 90"}).build();
client.index("movie_ratings").search(searchRequest);
```
```ruby Ruby
client.index('movies_ratings').search('thriller', {
filter: 'rating.users >= 90'
})
```
```go Go
resp, err := client.Index("movie_ratings").Search("thriller", &meilisearch.SearchRequest{
Filter: "rating.users >= 90",
})
```
```csharp C#
var filters = new SearchQuery() { Filter = "rating.users >= 90" };
var movies = await client.Index("movie_ratings").SearchAsync("thriller", filters);
```
```rust Rust
let results: SearchResults = client
.index("movie_rating")
.search()
.with_query("thriller")
.with_filter("rating.users >= 90")
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "thriller",
filter: "rating.users >= 90"
)
client.index("movie_ratings").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movie_ratings').search(
'thriller',
SearchQuery(
filterExpression: Meili.gte(
//or Meili.attr('rating.users')
//or 'rating.users'.toMeiliAttribute()
Meili.attrFromParts(['rating', 'users']),
Meili.value(90),
),
),
);
```
You can also combine multiple conditions. For example, you can limit your search so it only includes `Batman` movies directed by either `Tim Burton` or `Christopher Nolan`:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movie_ratings/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "Batman",
"filter": "release_date > 795484800 AND (director = \"Tim Burton\" OR director = \"Christopher Nolan\")"
}'
```
```javascript JS
client.index('movie_ratings').search('Batman', {
filter: 'release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")'
})
```
```python Python
client.index('movie_ratings').search('Batman', {
'filter': 'release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")'
})
```
```php PHP
$client->index('movie_ratings')->search('Batman', [
'filter' => 'release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")'
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("Batman").filter(new String[] {"release_date > 795484800 AND (director = \"Tim Burton\" OR director = \"Christopher Nolan\")"}).build();
client.index("movie_ratings").search(searchRequest);
```
```ruby Ruby
client.index('movie_ratings').search('Batman', {
filter: 'release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")'
})
```
```go Go
resp, err := client.Index("movie_ratings").Search("Batman", &meilisearch.SearchRequest{
Filter: "release_date > 795484800 AND (director = \"Tim Burton\" OR director = \"Christopher Nolan\")",
})
```
```csharp C#
SearchQuery filters = new SearchQuery() { Filter = "release_date > 795484800 AND (director =
\"Tim Burton\" OR director = \"Christopher Nolan\")" };
var movies = await client.Index("movie_ratings").SearchAsync("Batman", filters);
```
```rust Rust
let results: SearchResults = client
.index("movie_ratings")
.search()
.with_query("Batman")
.with_filter(r#"release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")"#)
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "Batman",
filter: "release_date > 795484800 AND (director = \"Tim Burton\" OR director = \"Christopher Nolan\"")
client.index("movie_ratings").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movie_ratings').search(
'Batman',
SearchQuery(
filterExpression: Meili.and([
Meili.attr('release_date')
.gt(DateTime.utc(1995, 3, 18).toMeiliValue()),
Meili.or([
'director'.toMeiliAttribute().eq('Tim Burton'.toMeiliValue()),
'director'
.toMeiliAttribute()
.eq('Christopher Nolan'.toMeiliValue()),
]),
]),
),
);
```
Here, the parentheses are mandatory: without them, the filter would return movies directed by `Tim Burton` and released after 1995 or any film directed by `Christopher Nolan`, without constraints on its release date. This happens because `AND` takes precedence over `OR`.
If you only want recent `Planet of the Apes` movies that weren't directed by `Tim Burton`, you can use this filter:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movie_ratings/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "Planet of the Apes",
"filter": "release_date > 1577884550 AND (NOT director = \"Tim Burton\")"
}' \
```
```javascript JS
client.index('movie_ratings').search('Planet of the Apes', {
filter: "release_date > 1577884550 AND (NOT director = \"Tim Burton\")"
})
```
```python Python
client.index('movie_ratings').search('Planet of the Apes', {
'filter': 'release_date > 1577884550 AND (NOT director = "Tim Burton"))'
})
```
```php PHP
$client->index('movie_ratings')->search('Planet of the Apes', [
'filter' => 'release_date > 1577884550 AND (NOT director = "Tim Burton")'
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("Planet of the Apes").filter(new String[] {"release_date > 1577884550 AND (NOT director = \"Tim Burton\")"}).build();
client.index("movie_ratings").search(searchRequest);
```
```ruby Ruby
client.index('movie_ratings').search('Planet of the Apes', {
filter: "release_date > 1577884550 AND (NOT director = \"Tim Burton\")"
})
```
```go Go
resp, err := client.Index("movie_ratings").Search("Planet of the Apes", &meilisearch.SearchRequest{
Filter: "release_date > 1577884550 AND (NOT director = \"Tim Burton\")",
})
```
```csharp C#
SearchQuery filters = new SearchQuery() { Filter = "release_date > 1577884550 AND (NOT director = \"Tim Burton\")" };
var movies = await client.Index("movie_ratings").SearchAsync("Planet of the Apes", filters);
```
```rust Rust
let results: SearchResults = client
.index("movie_ratings")
.search()
.with_query("Planet of the Apes")
.with_filter(r#"release_date > 1577884550 AND (NOT director = "Tim Burton")"#)
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "Planet of the Apes",
filter: "release_date > 1577884550 AND (NOT director = \"Tim Burton\"))
client.index("movie_ratings").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movie_ratings').search(
'Planet of the Apes',
SearchQuery(
filterExpression: Meili.and([
Meili.attr('release_date')
.gt(DateTime.utc(2020, 1, 1, 13, 15, 50).toMeiliValue()),
Meili.not(
Meili.attr('director').eq("Tim Burton".toMeiliValue()),
),
]),
),
);
```
```
release_date > 1577884550 AND (NOT director = "Tim Burton" AND director EXISTS)
```
[Synonyms](/learn/relevancy/synonyms) don't apply to filters. Meaning, if you have `SF` and `San Francisco` set as synonyms, filtering by `SF` and `San Francisco` will show you different results.
# Geosearch
Source: https://www.meilisearch.com/docs/learn/filtering_and_sorting/geosearch
Filter and sort search results based on their geographic location.
Meilisearch allows you to filter and sort results based on their geographic location. This can be useful when you only want results within a specific area or when sorting results based on their distance from a specific location.
Due to Meilisearch allowing malformed `_geo` fields in the following versions (v0.27, v0.28 and v0.29), please ensure the `_geo` field follows the correct format.
## Preparing documents for location-based search
In order to start filtering and sorting documents based on their geographic location, you must make sure they contain a valid `_geo` field.
`_geo` is a reserved field. If you include it in your documents, Meilisearch expects its value to conform to a specific format.
When using JSON and NDJSON, `_geo` must contain an object with two keys: `lat` and `lng`. Both fields must contain either a floating point number or a string indicating, respectively, latitude and longitude:
```json
{
…
"_geo": {
"lat": 0.0,
"lng": "0.0"
}
}
```
### Examples
Suppose we have a JSON array containing a few restaurants:
```json
[
{
"id": 1,
"name": "Nàpiz' Milano",
"address": "Viale Vittorio Veneto, 30, 20124, Milan, Italy",
"type": "pizza",
"rating": 9
},
{
"id": 2,
"name": "Bouillon Pigalle",
"address": "22 Bd de Clichy, 75018 Paris, France",
"type": "french",
"rating": 8
},
{
"id": 3,
"name": "Artico Gelateria Tradizionale",
"address": "Via Dogana, 1, 20123 Milan, Italy",
"type": "ice cream",
"rating": 10
}
]
```
Our restaurant dataset looks like this once we add geopositioning data:
```json
[
{
"id": 1,
"name": "Nàpiz' Milano",
"address": "Viale Vittorio Veneto, 30, 20124, Milan, Italy",
"type": "pizza",
"rating": 9,
"_geo": {
"lat": 45.4777599,
"lng": 9.1967508
}
},
{
"id": 2,
"name": "Bouillon Pigalle",
"address": "22 Bd de Clichy, 75018 Paris, France",
"type": "french",
"rating": 8,
"_geo": {
"lat": 48.8826517,
"lng": 2.3352748
}
},
{
"id": 3,
"name": "Artico Gelateria Tradizionale",
"address": "Via Dogana, 1, 20123 Milan, Italy",
"type": "ice cream",
"rating": 10,
"_geo": {
"lat": 45.4632046,
"lng": 9.1719421
}
}
]
```
Trying to index a dataset with one or more documents containing badly formatted `_geo` values will cause Meilisearch to throw an [`invalid_document_geo_field`](/reference/errors/error_codes#invalid_document_geo_field) error. In this case, the update will fail and no documents will be added or modified.
### Using `_geo` with CSV
If your dataset is formatted as CSV, the file header must have a `_geo` column. Each row in the dataset must then contain a column with a comma-separated string indicating latitude and longitude:
```csv
"id:number","name:string","address:string","type:string","rating:number","_geo:string"
"1","Nàpiz Milano","Viale Vittorio Veneto, 30, 20124, Milan, Italy","pizzeria",9,"45.4777599,9.1967508"
"2","Bouillon Pigalle","22 Bd de Clichy, 75018 Paris, France","french",8,"48.8826517,2.3352748"
"3","Artico Gelateria Tradizionale","Via Dogana, 1, 20123 Milan, Italy","ice cream",10,"48.8826517,2.3352748"
```
## Filtering results with `_geoRadius` and `_geoBoundingBox`
You can use `_geo` data to filter queries so you only receive results located within a given geographic area.
### Configuration
In order to filter results based on their location, you must add the `_geo` attribute to the `filterableAttributes` list:
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/restaurants/settings/filterable-attributes' \
-H 'Content-type:application/json' \
--data-binary '["_geo"]'
```
```javascript JS
client.index('restaurants')
.updateFilterableAttributes([
'_geo'
])
```
```python Python
client.index('restaurants').update_filterable_attributes([
'_geo'
])
```
```php PHP
$client->index('restaurants')->updateFilterableAttributes([
'_geo'
]);
```
```java Java
Settings settings = new Settings();
settings.setFilterableAttributes(new String[] {"_geo"});
client.index("restaurants").updateSettings(settings);
```
```ruby Ruby
client.index('restaurants').update_filterable_attributes(['_geo'])
```
```go Go
filterableAttributes := []interface{}{
"_geo",
}
client.Index("restaurants").UpdateFilterableAttributes(&filterableAttributes)
```
```csharp C#
List attributes = new() { "_geo" };
TaskInfo result = await client.Index("movies").UpdateFilterableAttributesAsync(attributes);
```
```rust Rust
let task: TaskInfo = client
.index("restaurants")
.set_filterable_attributes(&["_geo"])
.await
.unwrap();
```
```swift Swift
client.index("restaurants").updateFilterableAttributes(["_geo"]) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('restaurants').updateFilterableAttributes(['_geo']);
```
Meilisearch will rebuild your index whenever you update `filterableAttributes`. Depending on the size of your dataset, this might take a considerable amount of time.
[You can read more about configuring `filterableAttributes` in our dedicated filtering guide.](/learn/filtering_and_sorting/filter_search_results)
### Usage
Use the [`filter` search parameter](/reference/api/search#filter) along with `_geoRadius` or `_geoBoundingBox`. These are special filter rules that ensure Meilisearch only returns results located within a specific geographic area.
### `_geoRadius`
```
_geoRadius(lat, lng, distance_in_meters)
```
### `_geoBoundingBox`
```
_geoBoundingBox([{lat}, {lng}], [{lat}, {lng}])
```
### Examples
Using our example dataset, we can search for places to eat near the center of Milan with `_geoRadius`:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/restaurants/search' \
-H 'Content-type:application/json' \
--data-binary '{ "filter": "_geoRadius(45.472735, 9.184019, 2000)" }'
```
```javascript JS
client.index('restaurants').search('', {
filter: ['_geoRadius(45.472735, 9.184019, 2000)'],
})
```
```python Python
client.index('restaurants').search('', {
'filter': '_geoRadius(45.472735, 9.184019, 2000)'
})
```
```php PHP
$client->index('restaurants')->search('', [
'filter' => '_geoRadius(45.472735, 9.184019, 2000)'
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("").filter(new String[] {"_geoRadius(45.472735, 9.184019, 2000)"}).build();
client.index("restaurants").search(searchRequest);
```
```ruby Ruby
client.index('restaurants').search('', { filter: '_geoRadius(45.472735, 9.184019, 2000)' })
```
```go Go
resp, err := client.Index("restaurants").Search("", &meilisearch.SearchRequest{
Filter: "_geoRadius(45.472735, 9.184019, 2000)",
})
```
```csharp C#
SearchQuery filters = new SearchQuery() { Filter = "_geoRadius(45.472735, 9.184019, 2000)" };
var restaurants = await client.Index("restaurants").SearchAsync("", filters);
```
```rust Rust
let results: SearchResults = client
.index("restaurants")
.search()
.with_filter("_geoRadius(45.472735, 9.184019, 2000)")
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
filter: "_geoRadius(45.472735, 9.184019, 2000)"
)
client.index("restaurants").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('restaurants').search(
'',
SearchQuery(
filterExpression: Meili.geoRadius(
(lat: 45.472735, lng: 9.184019),
2000,
),
),
);
```
We also make a similar query using `_geoBoundingBox`:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/restaurants/search' \
-H 'Content-type:application/json' \
--data-binary '{ "filter": "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])" }'
```
```javascript JS
client.index('restaurants').search('', {
filter: ['_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])'],
})
```
```python Python
client.index('restaurants').search('Batman', {
'filter': '_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])'
})
```
```php PHP
$client->index('restaurants')->search('', [
'filter' => '_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])'
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q()("").filter(new String[] {
"_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])"
}).build();
client.index("restaurants").search(searchRequest);
```
```ruby Ruby
client.index('restaurants').search('', { filter: ['_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])'] })
```
```go Go
client.Index("restaurants").Search("", &meilisearch.SearchRequest{
Filter: "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])",
})
```
```csharp C#
SearchQuery filters = new SearchQuery()
{
Filter = "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])"
};
var restaurants = await client.Index("restaurants").SearchAsync("restaurants", filters);
```
```rust Rust
let results: SearchResults = client
.index("restaurants")
.search()
.with_filter("_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])")
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
filter: "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])"
)
client.index("restaurants").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('restaurants').search(
'',
SearchQuery(
filter:
'_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])',
),
);
```
```json
[
{
"id": 1,
"name": "Nàpiz' Milano",
"address": "Viale Vittorio Veneto, 30, 20124, Milan, Italy",
"type": "pizza",
"rating": 9,
"_geo": {
"lat": 45.4777599,
"lng": 9.1967508
}
},
{
"id": 3,
"name": "Artico Gelateria Tradizionale",
"address": "Via Dogana, 1, 20123 Milan, Italy",
"type": "ice cream",
"rating": 10,
"_geo": {
"lat": 45.4632046,
"lng": 9.1719421
}
}
]
```
It is also possible to combine `_geoRadius` and `_geoBoundingBox` with other filters. We can narrow down our previous search so it only includes pizzerias:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/restaurants/search' \
-H 'Content-type:application/json' \
--data-binary '{ "filter": "_geoRadius(45.472735, 9.184019, 2000) AND type = pizza" }'
```
```javascript JS
client.index('restaurants').search('', {
filter: ['_geoRadius(45.472735, 9.184019, 2000) AND type = pizza'],
})
```
```python Python
client.index('restaurants').search('', {
'filter': '_geoRadius(45.472735, 9.184019, 2000) AND type = pizza'
})
```
```php PHP
$client->index('restaurants')->search('', [
'filter' => '_geoRadius(45.472735, 9.184019, 2000) AND type = pizza'
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("").filter(new String[] {"_geoRadius(45.472735, 9.184019, 2000) AND type = pizza"}).build();
client.index("restaurants").search(searchRequest);
```
```ruby Ruby
client.index('restaurants').search('', { filter: '_geoRadius(45.472735, 9.184019, 2000) AND type = pizza' })
```
```go Go
resp, err := client.Index("restaurants").Search("", &meilisearch.SearchRequest{
Filter: "_geoRadius(45.472735, 9.184019, 2000) AND type = pizza",
})
```
```csharp C#
SearchQuery filters = new SearchQuery()
{
Filter = new string[] { "_geoRadius(45.472735, 9.184019, 2000) AND type = pizza" }
};
var restaurants = await client.Index("restaurants").SearchAsync("restaurants", filters);
```
```rust Rust
let results: SearchResults = client
.index("restaurants")
.search()
.with_filter("_geoRadius(45.472735, 9.184019, 2000) AND type = pizza")
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
filter: "_geoRadius(45.472735, 9.184019, 2000) AND type = pizza"
)
client.index("restaurants").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('restaurants').search(
'',
SearchQuery(
filterExpression: Meili.and([
Meili.geoRadius(
(lat: 45.472735, lng: 9.184019),
2000,
),
Meili.attr('type').eq('pizza'.toMeiliValue())
]),
),
);
```
```json
[
{
"id": 1,
"name": "Nàpiz' Milano",
"address": "Viale Vittorio Veneto, 30, 20124, Milan, Italy",
"type": "pizza",
"rating": 9,
"_geo": {
"lat": 45.4777599,
"lng": 9.1967508
}
}
]
```
`_geo`, `_geoDistance`, and `_geoPoint` are not valid filter rules. Trying to use any of them with the `filter` search parameter will result in an [`invalid_search_filter`](/reference/errors/error_codes#invalid_search_filter) error.
## Sorting results with `_geoPoint`
### Configuration
Before using geosearch for sorting, you must add the `_geo` attribute to the `sortableAttributes` list:
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/restaurants/settings/sortable-attributes' \
-H 'Content-type:application/json' \
--data-binary '["_geo"]'
```
```javascript JS
client.index('restaurants').updateSortableAttributes([
'_geo'
])
```
```python Python
client.index('restaurants').update_sortable_attributes([
'_geo'
])
```
```php PHP
$client->index('restaurants')->updateSortableAttributes([
'_geo'
]);
```
```java Java
client.index("restaurants").updateSortableAttributesSettings(new String[] {"_geo"});
```
```ruby Ruby
client.index('restaurants').update_sortable_attributes(['_geo'])
```
```go Go
sortableAttributes := []string{
"_geo",
}
client.Index("restaurants").UpdateSortableAttributes(&sortableAttributes)
```
```csharp C#
List attributes = new() { "_geo" };
TaskInfo result = await client.Index("restaurants").UpdateSortableAttributesAsync(attributes);
```
```rust Rust
let task: TaskInfo = client
.index("restaurants")
.set_sortable_attributes(&["_geo"])
.await
.unwrap();
```
```swift Swift
client.index("restaurants").updateSortableAttributes(["_geo"]) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('restaurants').updateSortableAttributes(['_geo']);
```
[Read more about `sortableAttributes` here.](/learn/filtering_and_sorting/sort_search_results)
### Usage
```
_geoPoint(0.0, 0.0):asc
```
### Examples
The `_geoPoint` sorting function can be used like any other sorting rule. We can order documents based on how close they are to the Eiffel Tower:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/restaurants/search' \
-H 'Content-type:application/json' \
--data-binary '{ "sort": ["_geoPoint(48.8561446,2.2978204):asc"] }'
```
```javascript JS
client.index('restaurants').search('', {
sort: ['_geoPoint(48.8561446, 2.2978204):asc'],
})
```
```python Python
client.index('restaurants').search('', {
'sort': ['_geoPoint(48.8561446,2.2978204):asc']
})
```
```php PHP
$client->index('restaurants')->search('', [
'sort' => ['_geoPoint(48.8561446,2.2978204):asc']
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("").sort(new String[] {"_geoPoint(48.8561446,2.2978204):asc"}).build();
client.index("restaurants").search(searchRequest);
```
```ruby Ruby
client.index('restaurants').search('', { sort: ['_geoPoint(48.8561446, 2.2978204):asc'] })
```
```go Go
resp, err := client.Index("restaurants").Search("", &meilisearch.SearchRequest{
Sort: []string{
"_geoPoint(48.8561446,2.2978204):asc",
},
})
```
```csharp C#
SearchQuery filters = new SearchQuery()
{
Sort = new string[] { "_geoPoint(48.8561446,2.2978204):asc" }
};
var restaurants = await client.Index("restaurants").SearchAsync("", filters);
```
```rust Rust
let results: SearchResults = client
.index("restaurants")
.search()
.with_sort(&["_geoPoint(48.8561446, 2.2978204):asc"])
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "",
sort: ["_geoPoint(48.8561446, 2.2978204):asc"]
)
client.index("restaurants").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('restaurants').search(
'', SearchQuery(sort: ['_geoPoint(48.8561446, 2.2978204):asc']));
```
With our restaurants dataset, the results look like this:
```json
[
{
"id": 2,
"name": "Bouillon Pigalle",
"address": "22 Bd de Clichy, 75018 Paris, France",
"type": "french",
"rating": 8,
"_geo": {
"lat": 48.8826517,
"lng": 2.3352748
}
},
{
"id": 3,
"name": "Artico Gelateria Tradizionale",
"address": "Via Dogana, 1, 20123 Milan, Italy",
"type": "ice cream",
"rating": 10,
"_geo": {
"lat": 45.4632046,
"lng": 9.1719421
}
},
{
"id": 1,
"name": "Nàpiz' Milano",
"address": "Viale Vittorio Veneto, 30, 20124, Milan, Italy",
"type": "pizza",
"rating": 9,
"_geo": {
"lat": 45.4777599,
"lng": 9.1967508
}
}
]
```
`_geoPoint` also works when used together with other sorting rules. We can sort restaurants based on their proximity to the Eiffel Tower and their rating:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/restaurants/search' \
-H 'Content-type:application/json' \
--data-binary '{
"sort": [
"_geoPoint(48.8561446,2.2978204):asc",
"rating:desc"
]
}'
```
```javascript JS
client.index('restaurants').search('', {
sort: ['_geoPoint(48.8561446, 2.2978204):asc', 'rating:desc'],
})
```
```python Python
client.index('restaurants').search('', {
'sort': ['_geoPoint(48.8561446,2.2978204):asc', 'rating:desc']
})
```
```php PHP
$client->index('restaurants')->search('', [
'sort' => ['_geoPoint(48.8561446,2.2978204):asc', 'rating:desc']
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q()("").sort(new String[] {
"_geoPoint(48.8561446,2.2978204):asc",
"rating:desc",
}).build();
client.index("restaurants").search(searchRequest);
```
```ruby Ruby
client.index('restaurants').search('', { sort: ['_geoPoint(48.8561446, 2.2978204):asc', 'rating:desc'] })
```
```go Go
resp, err := client.Index("restaurants").Search("", &meilisearch.SearchRequest{
Sort: []string{
"_geoPoint(48.8561446,2.2978204):asc",
"rating:desc",
},
})
```
```csharp C#
SearchQuery filters = new SearchQuery()
{
Sort = new string[] {
"_geoPoint(48.8561446,2.2978204):asc",
"rating:desc"
}
};
var restaurants = await client.Index("restaurants").SearchAsync("restaurants", filters);
```
```rust Rust
let results: SearchResults = client
.index("restaurants")
.search()
.with_sort(&["_geoPoint(48.8561446, 2.2978204):asc", "rating:desc"])
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "",
sort: ["_geoPoint(48.8561446, 2.2978204):asc", "rating:desc"]
)
client.index("restaurants").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('restaurants').search(
'',
SearchQuery(
sort: ['_geoPoint(48.8561446, 2.2978204):asc', 'rating:desc']));
```
```json
[
{
"id": 2,
"name": "Bouillon Pigalle",
"address": "22 Bd de Clichy, 75018 Paris, France",
"type": "french",
"rating": 8,
"_geo": {
"lat": 48.8826517,
"lng": 2.3352748
}
},
{
"id": 3,
"name": "Artico Gelateria Tradizionale",
"address": "Via Dogana, 1, 20123 Milan, Italy",
"type": "ice cream",
"rating": 10,
"_geo": {
"lat": 45.4632046,
"lng": 9.1719421
}
},
{
"id": 1,
"name": "Nàpiz' Milano",
"address": "Viale Vittorio Veneto, 30, 20124, Milan, Italy",
"type": "pizza",
"rating": 9,
"_geo": {
"lat": 45.4777599,
"lng": 9.1967508
}
}
]
```
# Search with facets
Source: https://www.meilisearch.com/docs/learn/filtering_and_sorting/search_with_facet_filters
Faceted search interfaces provide users with a quick way to narrow down search results by selecting categories relevant to their query.
In Meilisearch, facets are a specialized type of filter. This guide shows you how to configure facets and use them when searching a database of books. It also gives you instruction on how to get
## Requirements
* a Meilisearch project
* a command-line terminal
## Configure facet index settings
First, create a new index using this books dataset. Documents in this dataset have the following fields:
```json
{
"id": 5,
"title": "Hard Times",
"genres": ["Classics","Fiction", "Victorian", "Literature"],
"publisher": "Penguin Classics",
"language": "English",
"author": "Charles Dickens",
"description": "Hard Times is a novel of social […] ",
"format": "Hardcover",
"rating": 3
}
```
Next, add `genres`, `language`, and `rating` to the list of `filterableAttributes`:
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/books/settings/filterable-attributes' \
-H 'Content-Type: application/json' \
--data-binary '[
"genres", "rating", "language"
]'
```
```javascript JS
client.index('movie_ratings').updateFilterableAttributes(['genres', 'rating', 'language'])
```
```python Python
client.index('movie_ratings').update_filterable_attributes([
'genres',
'director',
'language'
])
```
```php PHP
$client->index('movie_ratings')->updateFilterableAttributes(['genres', 'rating', 'language']);
```
```java Java
client.index("movie_ratings").updateFilterableAttributesSettings(new String[] { "genres", "director", "language" });
```
```ruby Ruby
client.index('movie_ratings').update_filterable_attributes(['genres', 'rating', 'language'])
```
```go Go
filterableAttributes := []interface{}{
"genres",
"rating",
"language",
}
client.Index("movie_ratings").UpdateFilterableAttributes(&filterableAttributes)
```
```csharp C#
List attributes = new() { "genres", "rating", "language" };
TaskInfo result = await client.Index("movie_ratings").UpdateFilterableAttributesAsync(attributes);
```
```rust Rust
let task: TaskInfo = client
.index("movie_ratings")
.set_filterable_attributes(&["genres", "rating", "language"])
.await
.unwrap();
```
```dart Dart
await client
.index('movie_ratings')
.updateFilterableAttributes(['genres', 'rating', 'language']);
```
You have now configured your index to use these attributes as filters.
## Use facets in a search query
Make a search query setting the `facets` search parameter:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/books/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "classic",
"facets": [
"genres", "rating", "language"
]
}'
```
```javascript JS
client.index('books').search('classic', { facets: ['genres', 'rating', 'language'] })
```
```python Python
client.index('books').search('classic', {
'facets': ['genres', 'rating', 'language']
})
```
```php PHP
$client->index('books')->search('classic', [
'facets' => ['genres', 'rating', 'language']
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("classic").facets(new String[]
{
"genres",
"rating",
"language"
}).build();
client.index("books").search(searchRequest);
```
```ruby Ruby
client.index('books').search('classic', {
facets: ['genres', 'rating', 'language']
})
```
```go Go
resp, err := client.Index("books").Search("classic", &meilisearch.SearchRequest{
Facets: []string{
"genres",
"rating",
"language",
},
})
```
```csharp C#
var sq = new SearchQuery
{
Facets = new string[] { "genres", "rating", "language" }
};
await client.Index("books").SearchAsync("classic", sq);
```
```rust Rust
let books = client.index("books");
let results: SearchResults = SearchQuery::new(&books)
.with_query("classic")
.with_facets(Selectors::Some(&["genres", "rating", "language"]))
.execute()
.await
.unwrap();
```
```dart Dart
await client
.index('books')
.search('', SearchQuery(facets: ['genres', 'rating', 'language']));
```
The response returns all books matching the query. It also returns two fields you can use to create a faceted search interface, `facetDistribution` and `facetStats`:
```json
{
"hits": [
…
],
…
"facetDistribution": {
"genres": {
"Classics": 6,
…
},
"language": {
"English": 6,
"French": 1,
"Spanish": 1
},
"rating": {
"2.5": 1,
…
}
},
"facetStats": {
"rating": {
"min": 2.5,
"max": 4.7
}
}
}
```
`facetDistribution` lists all facets present in your search results, along with the number of documents returned for each facet.
`facetStats` contains the highest and lowest values for all facets containing numeric values.
### Sorting facet values
By default, all facet values are sorted in ascending alphanumeric order. You can change this using the `sortFacetValuesBy` property of the [`faceting` index settings](/reference/api/settings#faceting):
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/indexes/books/settings/faceting' \
-H 'Content-Type: application/json' \
--data-binary '{
"sortFacetValuesBy": {
"genres": "count"
}
}'
```
```javascript JS
client.index('books').updateFaceting({
sortFacetValuesBy: {
genres: 'count'
}
})
```
```python Python
client.index('books').update_faceting_settings({ 'sortFacetValuesBy': { 'genres': 'count' } })
```
```php PHP
$client->index('books')->updateFaceting(['sortFacetValuesBy' => ['genres' => 'count']]);
```
```java Java
Faceting newFaceting = new Faceting();
HashMap facetSortValues = new HashMap<>();
facetSortValues.put("genres", FacetSortValue.COUNT);
newFaceting.setSortFacetValuesBy(facetSortValues);
client.index("books").updateFacetingSettings(newFaceting);
```
```ruby Ruby
client.index('books').update_faceting(
sort_facet_values_by: {
genres: 'count'
}
)
```
```go Go
client.Index("books").UpdateFaceting(&meilisearch.Faceting{
SortFacetValuesBy: {
"genres": SortFacetTypeCount,
}
})
```
```csharp C#
var newFaceting = new Faceting
{
SortFacetValuesBy = new Dictionary
{
["genres"] = SortFacetValuesByType.Count
}
};
await client.Index("books").UpdateFacetingAsync(newFaceting);
```
```rust Rust
let mut facet_sort_setting = BTreeMap::new();
facet_sort_setting.insert("genres".to_string(), FacetSortValue::Count);
let faceting = FacetingSettings {
max_values_per_facet: 100,
sort_facet_values_by: Some(facet_sort_setting),
};
let res = client.index("books")
.set_faceting(&faceting)
.await
.unwrap();
```
```dart Dart
await client.index('books').updateFaceting(
Faceting(
sortFacetValuesBy: {
'genres': FacetingSortTypes.count,
},
),
);
```
The above code sample sorts the `genres` facet by descending value count.
Repeating the previous query using the new settings will result in a different order in `facetsDistribution`:
```json
{
…
"facetDistribution": {
"genres": {
"Fiction": 8,
"Literature": 7,
"Classics": 6,
"Novel": 2,
"Horror": 2,
"Fantasy": 2,
"Victorian": 2,
"Vampires": 1,
"Tragedy": 1,
"Satire": 1,
"Romance": 1,
"Historical Fiction": 1,
"Coming-of-Age": 1,
"Comedy": 1
},
…
}
}
```
## Searching facet values
You can also search for facet values with the [facet search endpoint](/reference/api/facet_search):
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/books/facet-search' \
-H 'Content-Type: application/json' \
--data-binary '{
"facetQuery": "c",
"facetName": "genres"
}'
```
```javascript JS
client.index('books').searchForFacetValues({
facetQuery: 'c',
facetName: 'genres'
})
```
```python Python
client.index('books').facet_search('genres', 'c')
```
```php PHP
$client->index('books')->facetSearch(
(new FacetSearchQuery())
->setFacetQuery('c')
->setFacetName('genres')
);
```
```java Java
FacetSearchRequest fsr = FacetSearchRequest.builder().facetName("genres").facetQuery("c").build();
client.index("books").facetSearch(fsr);
```
```ruby Ruby
client.index('books').facet_search('genres', 'c')
```
```go Go
client.Index("books").FacetSearch(&meilisearch.FacetSearchRequest{
FacetQuery: "c",
FacetName: "genres",
ExhaustiveFacetCount: true
})
```
```csharp C#
var query = new SearchFacetsQuery()
{
FacetQuery = "c"
};
await client.Index("books").FacetSearchAsync("genres", query);
```
```rust Rust
let res = client.index("books")
.facet_search("genres")
.with_facet_query("c")
.execute()
.await
.unwrap();
```
```dart Dart
await client.index('books').facetSearch(
FacetSearchQuery(
facetQuery: 'c',
facetName: 'genres',
),
);
```
The following code sample searches the `genres` facet for values starting with `c`:
The response contains a `facetHits` array listing all matching facets, together with the total number of documents that include that facet:
```json
{
…
"facetHits": [
{
"value": "Children's Literature",
"count": 1
},
{
"value": "Classics",
"count": 6
},
{
"value": "Comedy",
"count": 2
},
{
"value": "Coming-of-Age",
"count": 1
}
],
"facetQuery": "c",
…
}
```
You can further refine results using the `q`, `filter`, and `matchingStrategy` parameters. [Learn more about them in the API reference.](/reference/api/facet_search)
# Sort search results
Source: https://www.meilisearch.com/docs/learn/filtering_and_sorting/sort_search_results
By default, Meilisearch sorts results according to their relevancy. You can alter this behavior so users can decide at search time results they want to see first.
By default, Meilisearch focuses on ordering results according to their relevancy. You can alter this sorting behavior so users can decide at search time what type of results they want to see first.
This can be useful in many situations, such as when a user wants to see the cheapest products available in a webshop.
Sorting at search time can be particularly effective when combined with [placeholder searches](/reference/api/search#placeholder-search).
## Configure Meilisearch for sorting at search time
To allow your users to sort results at search time you must:
1. Decide which attributes you want to use for sorting
2. Add those attributes to the `sortableAttributes` index setting
3. Update Meilisearch's [ranking rules](/learn/relevancy/relevancy) (optional)
Meilisearch sorts strings in lexicographic order based on their byte values. For example, `á`, which has a value of 225, will be sorted after `z`, which has a value of 122.
Uppercase letters are sorted as if they were lowercase. They will still appear uppercase in search results.
### Add attributes to `sortableAttributes`
Meilisearch allows you to sort results based on document fields. Only fields containing numbers, strings, arrays of numeric values, and arrays of string values can be used for sorting.
After you have decided which fields you will allow your users to sort on, you must add their attributes to the [`sortableAttributes` index setting](/reference/api/settings#sortable-attributes).
If a field has values of different types across documents, Meilisearch will give precedence to numbers over strings. This means documents with numeric field values will be ranked higher than those with string values.
This can lead to unexpected behavior when sorting. For optimal user experience, only sort based on fields containing the same type of value.
#### Example
Suppose you have collection of books containing the following fields:
```json
[
{
"id": 1,
"title": "Solaris",
"author": "Stanislaw Lem",
"genres": [
"science fiction"
],
"rating": {
"critics": 95,
"users": 87
},
"price": 5.00
},
…
]
```
If you are using this dataset in a webshop, you might want to allow your users to sort on `author` and `price`:
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/books/settings/sortable-attributes' \
-H 'Content-Type: application/json' \
--data-binary '[
"author",
"price"
]'
```
```javascript JS
client.index('books').updateSortableAttributes([
'author',
'price'
])
```
```python Python
client.index('books').update_sortable_attributes([
'author',
'price'
])
```
```php PHP
$client->index('books')->updateSortableAttributes([
'author',
'price'
]);
```
```java Java
client.index("books").updateSortableAttributesSettings(new String[] {"price", "author"});
```
```ruby Ruby
client.index('books').update_sortable_attributes(['author', 'price'])
```
```go Go
sortableAttributes := []string{
"author",
"price",
}
client.Index("books").UpdateSortableAttributes(&sortableAttributes)
```
```csharp C#
await client.Index("books").UpdateSortableAttributesAsync(new [] { "price", "author" });
```
```rust Rust
let sortable_attributes = [
"author",
"price"
];
let task: TaskInfo = client
.index("books")
.set_sortable_attributes(&sortable_attributes)
.await
.unwrap();
```
```swift Swift
client.index("books").updateSortableAttributes(["price", "author"]) { (result: Result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('books').updateSortableAttributes(['author', 'price']);
```
### Customize ranking rule order (optional)
When users sort results at search time, [Meilisearch's ranking rules](/learn/relevancy/relevancy) are set up so the top matches emphasize relevant results over sorting order. You might need to alter this behavior depending on your application's needs.
This is the default configuration of Meilisearch's ranking rules:
```json
[
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness"
]
```
`"sort"` is in fifth place. This means it acts as a tie-breaker rule: Meilisearch will first place results closely matching search terms at the top of the returned documents list and only then will apply the `"sort"` parameters as requested by the user. In other words, by default Meilisearch provides a very relevant sorting.
Placing `"sort"` ranking rule higher in the list will emphasize exhaustive sorting over relevant sorting: your results will more closely follow the sorting order your user chose, but will not be as relevant.
Sorting applies equally to all documents. Meilisearch does not offer native support for promoting, pinning, and boosting specific documents so they are displayed more prominently than other search results. Consult these Meilisearch blog articles for workarounds on [implementing promoted search results with React InstantSearch](https://blog.meilisearch.com/promoted-search-results-with-react-instantsearch) and [document boosting](https://blog.meilisearch.com/document-boosting).
#### Example
If your users care more about finding cheaper books than they care about finding specific matches to their queries, you can place `sort` much higher in the ranking rules:
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/books/settings/ranking-rules' \
-H 'Content-Type: application/json' \
--data-binary '[
"words",
"sort",
"typo",
"proximity",
"attribute",
"exactness"
]'
```
```javascript JS
client.index('books').updateRankingRules([
'words',
'sort',
'typo',
'proximity',
'attribute',
'exactness'
])
```
```python Python
client.index('books').update_ranking_rules([
'words',
'sort',
'typo',
'proximity',
'attribute',
'exactness'
])
```
```php PHP
$client->index('books')->updateRankingRules([
'words',
'sort',
'typo',
'proximity',
'attribute',
'exactness'
]);
```
```java Java
Settings settings = new Settings();
settings.setRankingRules(new String[]
{
"words",
"sort",
"typo",
"proximity",
"attribute",
"exactness"
});
client.index("books").updateSettings(settings);
```
```ruby Ruby
client.index('books').update_ranking_rules([
'words',
'sort',
'typo',
'proximity',
'attribute',
'exactness'
])
```
```go Go
rankingRules := []string{
"words",
"sort",
"typo",
"proximity",
"attribute",
"exactness",
}
client.Index("books").UpdateRankingRules(&rankingRules)
```
```csharp C#
await client.Index("books").UpdateRankingRulesAsync(new[]
{
"words",
"sort",
"typo",
"proximity",
"attribute",
"exactness"
});
```
```rust Rust
let ranking_rules = [
"words",
"sort",
"typo",
"proximity",
"attribute",
"exactness"
];
let task: TaskInfo = client
.index("books")
.set_ranking_rules(&ranking_rules)
.await
.unwrap();
```
```swift Swift
let rankingRules: [String] = [
"words",
"sort",
"typo",
"proximity",
"attribute",
"exactness"
]
client.index("books").updateRankingRules(rankingRules) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('books').updateRankingRules(
['words', 'sort', 'typo', 'proximity', 'attribute', 'exactness']);
```
## Sort results at search time
After configuring `sortableAttributes`, you can use the [`sort` search parameter](/reference/api/search#sort) to control the sorting order of your search results.
`sort` expects a list of attributes that have been added to the `sortableAttributes` list.
Attributes must be given as `attribute:sorting_order`. In other words, each attribute must be followed by a colon (`:`) and a sorting order: either ascending (`asc`) or descending (`desc`).
When using the `POST` route, `sort` expects an array of strings:
```json
"sort": [
"price:asc",
"author:desc"
]
```
When using the `GET` route, `sort` expects a comma-separated string:
```
sort="price:desc,author:asc"
```
The order of `sort` values matter: the higher an attribute is in the search parameter value, the more Meilisearch will prioritize it over attributes placed lower. In our example, if multiple documents have the same value for `price`, Meilisearch will decide the order between these similarly-priced documents based on their `author`.
### Example
Suppose you are searching for books in a webshop and want to see the cheapest science fiction titles. This query searches for `"science fiction"` books sorted from cheapest to most expensive:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/books/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "science fiction",
"sort": ["price:asc"]
}'
```
```javascript JS
client.index('books').search('science fiction', {
sort: ['price:asc'],
})
```
```python Python
client.index('books').search('science fiction', {
'sort': ['price:asc']
})
```
```php PHP
$client->index('books')->search('science fiction', ['sort' => ['price:asc']]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("science fiction").sort(new String[] {"price:asc"}).build();
client.index("books").search(searchRequest);
```
```ruby Ruby
client.index('books').search('science fiction', { sort: ['price:asc'] })
```
```go Go
resp, err := client.Index("books").Search("science fiction", &meilisearch.SearchRequest{
Sort: []string{
"price:asc",
},
})
```
```csharp C#
var sq = new SearchQuery
{
Sort = new[] { "price:asc" },
};
await client.Index("books").SearchAsync("science fiction", sq);
```
```rust Rust
let results: SearchResults = client
.index("books")
.search()
.with_query("science fiction")
.with_sort(&["price:asc"])
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "science fiction",
sort: ["price:asc"]
)
client.index("books").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client
.index('books')
.search('science fiction', SearchQuery(sort: ['price:asc']));
```
With our example dataset, the results look like this:
```json
[
{
"id": 1,
"title": "Solaris",
"author": "Stanislaw Lem",
"genres": [
"science fiction"
],
"rating": {
"critics": 95,
"users": 87
},
"price": 5.00
},
{
"id": 2,
"title": "The Parable of the Sower",
"author": "Octavia E. Butler",
"genres": [
"science fiction"
],
"rating": {
"critics": 90,
"users": 92
},
"price": 10.00
}
]
```
It is common to search books based on an author's name. `sort` can help grouping results from the same author. This query would only return books matching the query term `"butler"` and group results according to their authors:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/books/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "butler",
"sort": ["author:desc"]
}'
```
```javascript JS
client.index('books').search('butler', {
sort: ['author:desc'],
})
```
```python Python
client.index('books').search('butler', {
'sort': ['author:desc']
})
```
```php PHP
$client->index('books')->search('butler', ['sort' => ['author:desc']]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("butler").sort(new String[] {"author:desc"}).build();
client.index("books").search(searchRequest);
```
```ruby Ruby
client.index('books').search('butler', { sort: ['author:desc'] })
```
```go Go
resp, err := client.Index("books").Search("butler", &meilisearch.SearchRequest{
Sort: []string{
"author:desc",
},
})
```
```csharp C#
var sq = new SearchQuery
{
Sort = new[] { "author:desc" },
};
await client.Index("books").SearchAsync("butler", sq);
```
```rust Rust
let results: SearchResults = client
.index("books")
.search()
.with_query("butler")
.with_sort(&["author:desc"])
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "butler",
sort: ["author:desc"]
)
client.index("books").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client
.index('books')
.search('butler', SearchQuery(sort: ['author:desc']));
```
```json
[
{
"id": 2,
"title": "The Parable of the Sower",
"author": "Octavia E. Butler",
"genres": [
"science fiction"
],
"rating": {
"critics": 90,
"users": 92
},
"price": 10.00
},
{
"id": 5,
"title": "Wild Seed",
"author": "Octavia E. Butler",
"genres": [
"fantasy"
],
"rating": {
"critics": 84,
"users": 80
},
"price": 5.00
},
{
"id": 4,
"title": "Gender Trouble",
"author": "Judith Butler",
"genres": [
"feminism",
"philosophy"
],
"rating": {
"critics": 86,
"users": 73
},
"price": 10.00
}
]
```
### Sort by nested fields
Use dot notation to sort results based on a document's nested fields. The following query sorts returned documents by their user review scores:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/books/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "science fiction",
"sort": ["rating.users:asc"]
}'
```
```javascript JS
client.index('books').search('science fiction', {
'sort': ['rating.users:asc'],
})
```
```python Python
client.index('books').search('science fiction', {
'sort': ['rating.users:asc']
})
```
```php PHP
$client->index('books')->search('science fiction', ['sort' => ['rating.users:asc']]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("science fiction").sort(new String[] {"rating.users:asc"}).build();
client.index("books").search(searchRequest);
```
```ruby Ruby
client.index('books').search('science fiction', { sort: ['rating.users:asc'] })
```
```go Go
resp, err := client.Index("books").Search("science fiction", &meilisearch.SearchRequest{
Sort: []string{
"rating.users:asc",
},
})
```
```csharp C#
SearchQuery sort = new SearchQuery() { Sort = new string[] { "rating.users:asc" }};
await client.Index("books").SearchAsync("science fiction", sort);
```
```rust Rust
let results: SearchResults = client
.index("books")
.search()
.with_query("science fiction")
.with_sort(&["rating.users:asc"])
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "science fiction",
sort: ["rating.users:asc"]
)
client.index("books").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client
.index('movie_ratings')
.search('thriller', SearchQuery(sort: ['rating.users:asc']));
```
## Sorting and custom ranking rules
There is a lot of overlap between sorting and configuring [custom ranking rules](/learn/relevancy/custom_ranking_rules), as both can greatly influence which results a user will see first.
Sorting is most useful when you want your users to be able to alter the order of returned results at query time. For example, webshop users might want to order results by price depending on what they are searching and to change whether they see the most expensive or the cheapest products first.
Custom ranking rules, instead, establish a default sorting rule that is enforced in every search. This approach can be useful when you want to promote certain results above all others, regardless of a user's preferences. For example, you might want a webshop to always feature discounted products first, no matter what a user is searching for.
## Example application
Take a look at our demos for examples of how to implement sorting:
* **Ecommerce demo**: [preview](https://ecommerce.meilisearch.com/) • [GitHub repository](https://github.com/meilisearch/ecommerce-demo/)
* **CRM SaaS demo**: [preview](https://saas.meilisearch.com/) • [GitHub repository](https://github.com/meilisearch/saas-demo/)
# Filtering and sorting by date
Source: https://www.meilisearch.com/docs/learn/filtering_and_sorting/working_with_dates
Learn how to index documents with chronological data, and how to sort and filter search results based on time.
In this guide, you will learn about Meilisearch's approach to date and time values, how to prepare your dataset for indexing, and how to chronologically sort and filter search results.
## Preparing your documents
To filter and sort search results chronologically, your documents must have at least one field containing a [UNIX timestamp](https://kb.narrative.io/what-is-unix-time). You may also use a string with a date in a format that can be sorted lexicographically, such as `"2025-01-13"`.
As an example, consider a database of video games. In this dataset, the release year is formatted as a timestamp:
```json
[
{
"id": 0,
"title": "Return of the Obra Dinn",
"genre": "adventure",
"release_timestamp": 1538949600
},
{
"id": 1,
"title": "The Excavation of Hob's Barrow",
"genre": "adventure",
"release_timestamp": 1664316000
},
{
"id": 2,
"title": "Bayonetta 2",
"genre": "action",
"release_timestamp": 1411164000
}
]
```
Once all documents in your dataset have a date field, [index your data](/reference/api/documents#add-or-replace-documents) as usual. The example below adds a videogame dataset to a `games` index:
```bash cURL
curl \
-x POST 'MEILISEARCH_URL/indexes/games/documents' \
-h 'content-type: application/json' \
--data-binary @games.json
```
```javascript JS
const games = require('./games.json')
client.index('games').addDocuments(games).then((res) => console.log(res))
```
```python Python
import json
json_file = open('./games.json', encoding='utf-8')
games = json.load(json_file)
client.index('games').add_documents(games)
```
```php PHP
$gamesJson = file_get_contents('games.json');
$games = json_decode($gamesJson);
$client->index('games')->addDocuments($games);
```
```java Java
import com.meilisearch.sdk;
import org.json.JSONArray;
import java.nio.file.Files;
import java.nio.file.Path;
Path fileName = Path.of("games.json");
String gamesJson = Files.readString(fileName);
Index index = client.index("games");
index.addDocuments(gamesJson);
```
```ruby Ruby
require 'json'
games = JSON.parse(File.read('games.json'))
client.index('games').add_documents(games)
```
```go Go
jsonFile, _ := os.Open("games.json")
defer jsonFile.Close()
byteValue, _ := io.ReadAll(jsonFile)
var games []map[string]interface{}
json.Unmarshal(byteValue, &games)
client.Index("games").AddDocuments(games, nil)
```
```csharp C#
string jsonString = await File.ReadAllTextAsync("games.json");
var games = JsonSerializer.Deserialize>(jsonString, options);
var index = client.Index("games");
await index.AddDocumentsAsync(games);
```
```rust Rust
let mut file = File::open("games.json")
.unwrap();
let mut content = String::new();
file
.read_to_string(&mut content)
.unwrap();
let docs: Vec = serde_json::from_str(&content)
.unwrap();
client
.index("games")
.add_documents(&docs, None)
.await
.unwrap();
```
```swift Swift
let path = Bundle.main.url(forResource: "games", withExtension: "json")!
let documents: Data = try Data(contentsOf: path)
client.index("games").addDocuments(documents: documents) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
//import 'dart:io';
//import 'dart:convert';
final json = await File('games.json').readAsString();
await client.index('games').addDocumentsJson(json);
```
## Filtering by date
To filter search results based on their timestamp, add your document's timestamp field to the list of [`filterableAttributes`](/reference/api/settings#update-filterable-attributes):
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/games/settings/filterable-attributes' \
-H 'Content-Type: application/json' \
--data-binary '[
"release_timestamp"
]'
```
```javascript JS
client.index('games').updateFilterableAttributes(['release_timestamp'])
```
```python Python
client.index('games').update_filterable_attributes(['release_timestamp'])
```
```php PHP
$client->index('games')->updateFilterableAttributes(['release_timestamp']);
```
```java Java
client.index("movies").updateFilterableAttributesSettings(new String[] { "release_timestamp" });
```
```ruby Ruby
client.index('games').update_filterable_attributes(['release_timestamp'])
```
```go Go
filterableAttributes := []interface{}{"release_timestamp"}
client.Index("games").UpdateFilterableAttributes(&filterableAttributes)
```
```csharp C#
await client.Index("games").UpdateFilterableAttributesAsync(new string[] { "release_timestamp" });
```
```rust Rust
let settings = Settings::new()
.with_filterable_attributes(["release_timestamp"]);
let task: TaskInfo = client
.index("games")
.set_settings(&settings)
.await
.unwrap();
```
```swift Swift
client.index("games").updateFilterableAttributes(["release_timestamp"]) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client
.index('games')
.updateFilterableAttributes(['release_timestamp']);
```
Once you have configured `filterableAttributes`, you can filter search results by date. The following query only returns games released between 2018 and 2022:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/games/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "",
"filter": "release_timestamp >= 1514761200 AND release_timestamp < 1672527600"
}'
```
```javascript JS
client.index('games').search('', {
filter: 'release_timestamp >= 1514761200 AND release_timestamp < 1672527600'
})
```
```python Python
client.index('games').search('', {
'filter': 'release_timestamp >= 1514761200 AND release_timestamp < 1672527600'
})
```
```php PHP
$client->index('games')->search('', [
'filter' => ['release_timestamp >= 1514761200 AND release_timestamp < 1672527600']
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("").filter(new String[] {"release_timestamp >= 1514761200 AND release_timestamp < 1672527600"}).build();
client.index("games").search(searchRequest);
```
```ruby Ruby
client.index('games').search('', {
filter: 'release_timestamp >= 1514761200 AND release_timestamp < 1672527600'
})
```
```go Go
client.Index("games").Search("", &meilisearch.SearchRequest{
Filter: "release_timestamp >= 1514761200 AND release_timestamp < 1672527600",
})
```
```csharp C#
var filters = new SearchQuery() { Filter = "release_timestamp >= 1514761200 AND release_timestamp < 1672527600" };
var games = await client.Index("games").SearchAsync("", filters);
```
```rust Rust
let results: SearchResults = client
.index("games")
.search()
.with_filter("release_timestamp >= 1514761200 AND release_timestamp < 1672527600")
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "",
filter: "release_timestamp >= 1514761200 AND release_timestamp < 1672527600"
)
client.index("games").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('games').search(
'',
SearchQuery(
filterExpression: Meili.and([
Meili.gte(
'release_timestamp'.toMeiliAttribute(),
Meili.value(DateTime(2017, 12, 31, 23, 0)),
),
Meili.lt(
'release_timestamp'.toMeiliAttribute(),
Meili.value(DateTime(2022, 12, 31, 23, 0)),
),
]),
),
);
```
## Sorting by date
To sort search results chronologically, add your document's timestamp field to the list of [`sortableAttributes`](/reference/api/settings#update-sortable-attributes):
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/games/settings/sortable-attributes' \
-H 'Content-Type: application/json' \
--data-binary '[
"release_timestamp"
]'
```
```javascript JS
client.index('games').updateSortableAttributes(['release_timestamp'])
```
```python Python
client.index('games').update_sortable_attributes(['release_timestamp'])
```
```php PHP
$client->index('games')->updateSortableAttributes(['release_timestamp']);
```
```java Java
Settings settings = new Settings();
settings.setSortableAttributes(new String[] {"release_timestamp"});
client.index("games").updateSettings(settings);
```
```ruby Ruby
client.index('games').update_sortable_attributes(['release_timestamp'])
```
```go Go
sortableAttributes := []string{"release_timestamp","author"}
client.Index("games").UpdateSortableAttributes(&sortableAttributes)
```
```csharp C#
await client.Index("games").UpdateSortableAttributesAsync(new string[] { "release_timestamp" });
```
```rust Rust
let settings = Settings::new()
.with_sortable_attributes(["release_timestamp"]);
let task: TaskInfo = client
.index("games")
.set_settings(&settings)
.await
.unwrap();
```
```swift Swift
client.index("games").updateSortableAttributes(["release_timestamp"]) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client
.index('games')
.updateSortableAttributes(['release_timestamp']);
```
Once you have configured `sortableAttributes`, you can sort your search results based on their timestamp. The following query returns all games sorted from most recent to oldest:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/games/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "",
"sort": ["release_timestamp:desc"]
}'
```
```javascript JS
client.index('games').search('', {
sort: ['release_timestamp:desc'],
})
```
```python Python
client.index('games').search('', {
'sort': ['release_timestamp:desc']
})
```
```php PHP
$client->index('games')->search('', ['sort' => ['release_timestamp:desc']]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("").sort(new String[] {"release_timestamp:desc"}).build();
client.index("games").search(searchRequest);
```
```ruby Ruby
client.index('games').search('', sort: ['release_timestamp:desc'])
```
```go Go
client.Index("games").Search("", &meilisearch.SearchRequest{
Sort: []string{
"release_timestamp:desc",
},
})
```
```csharp C#
SearchQuery sort = new SearchQuery() { Sort = new string[] { "release_timestamp:desc" }};
await client.Index("games").SearchAsync("", sort);
```
```rust Rust
let results: SearchResults = client
.index("games")
.search()
.with_sort(["release_timestamp:desc"])
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "",
sort: ["release_timestamp:desc"],
)
client.index("games").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client
.index('games')
.search('', SearchQuery(sort: ['release_timestamp:desc']));
```
# Getting started with Meilisearch Cloud
Source: https://www.meilisearch.com/docs/learn/getting_started/cloud_quick_start
Learn how to create your first Meilisearch Cloud project.
This tutorial walks you through setting up [Meilisearch Cloud](https://meilisearch.com/cloud), creating a project and an index, adding documents to it, and performing your first search with the default web interface.
You need a Meilisearch Cloud account to follow along. If you don't have one, register for a 14-day free trial account at [https://cloud.meilisearch.com/register](https://cloud.meilisearch.com/register?utm_campaign=oss\&utm_source=docs\&utm_medium=cloud-quick-start).
## Creating a project
To use Meilisearch Cloud, you must first create a project. Projects act as containers for indexes, tasks, billing, and other information related to Meilisearch Cloud.
Click the "New project" button on the top menu. If you have a free trial account and this is your first project, the button will read "Start free trial" instead:
Name your project `meilisearch-quick-start` and select the region closest to you, then click on "Create project":
If you are not using a free trial account, you must also choose a billing plan based on the size of your dataset and number of searches per month:
Creating your project might take a few minutes. Check the project list to follow its status. Once the project is ready, click on its name to go to the project overview page:
## Creating an index and adding documents
After creating your project, you must index the data you want to search. Meilisearch stores and processes data you add to it in indexes. A single project may contain multiple indexes.
First, click on the indexes tab in the project page menu:
This leads you to the index listing. Click on "New index":
Write `movies` in the name field and click on "Create Index":
The final step in creating an index is to add data to it. Choose "File upload":
Meilisearch Cloud will ask you for your dataset. To follow along, use this list of movies. Download the file to your computer, drag and drop it into the indicated area, then click on "Import documents":
Meilisearch Cloud will index your documents. This may take a moment. Click on "See index list" and wait. Once it is done, click on "Settings" to visit the index overview:
## Searching
With all data uploaded and processed, the last step is to run a few test searches to confirm Meilisearch is running as expected.
Click on the project name on the breadcrumb menu to return to the project overview:
Meilisearch Cloud comes with a search preview interface. Click on "Search preview" to access it:
Finally, try searching for a few movies, like "Solaris":
If you can see the results coming in as you type, congratulations: you now know all the basic steps to using Meilisearch Cloud.
## What's next
This tutorial taught you how to use Meilisearch Cloud's interface to create a project, add an index to it, and use the search preview interface.
In most real-life settings, you will be creating your own search interface and retrieving results through Meilisearch's API. To learn how to add documents and search using the command-line or an SDK in your preferred language, check out the [Meilisearch quick start](/learn/self_hosted/getting_started_with_self_hosted_meilisearch).
# Documents
Source: https://www.meilisearch.com/docs/learn/getting_started/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](/learn/getting_started/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](/reference/api/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](/learn/resources/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](/learn/engine/datatypes#objects).
With [ranking rules](/learn/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](/learn/filtering_and_sorting/geosearch) 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](/reference/api/settings#update-settings), or the respective update endpoints for [displayed attributes](/reference/api/settings#update-displayed-attributes), and [searchable attributes](/reference/api/settings#update-searchable-attributes) 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](/learn/relevancy/displayed_searchable_attributes#data-storing) in the document.
To learn more, refer to our [displayed and searchable attributes guide](/learn/relevancy/displayed_searchable_attributes).
## Primary field
The primary field is a special field that must be present in all documents. Its attribute is the [primary key](/learn/getting_started/primary_key#primary-field) and its value is the [document id](/learn/getting_started/primary_key#document-id). If you try to [index a document](/learn/self_hosted/getting_started_with_self_hosted_meilisearch#add-documents) 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](/learn/getting_started/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](/learn/self_hosted/configure_meilisearch_at_launch#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](/learn/resources/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](/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
{
"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
{ "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](/reference/api/documents#add-or-update-documents) or [add or replace documents](/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
"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`](/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.
# Indexes
Source: https://www.meilisearch.com/docs/learn/getting_started/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](/reference/api/indexes#create-an-index). Once created, you can add documents using the [add documents endpoint](/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
{
"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](/reference/api/indexes#update-an-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](/learn/getting_started/primary_key#setting-the-primary-key).
[Learn more about the primary field](/learn/getting_started/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](/reference/api/settings#update-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](/reference/api/settings#update-settings), or the respective endpoints for [displayed attributes](/reference/api/settings#update-displayed-attributes) and [searchable attributes](/reference/api/settings#update-searchable-attributes).
[Learn more about displayed and searchable attributes.](/learn/relevancy/displayed_searchable_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](/reference/api/settings#update-settings) or the [update distinct attribute endpoint](/reference/api/settings#update-distinct-attribute). **You can only set one field as the distinct attribute per index.**
[Learn more about distinct attributes.](/learn/relevancy/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`](/reference/api/settings#update-filterable-attributes), then make a search query using the [`filter` search parameter](/reference/api/search#filter).
By default, Meilisearch returns `100` facet values for each faceted field. You can change this using the [update settings endpoint](/reference/api/settings#update-settings) or the [update faceting settings endpoint](/reference/api/settings#update-faceting-settings).
[Learn more about faceting.](/learn/filtering_and_sorting/search_with_facet_filters)
### 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](/reference/api/settings#update-settings) or the [update filterable attributes endpoint](/reference/api/settings#update-filterable-attributes). Then, make a search query using the [`filter` search parameter](/reference/api/search#filter).
[Learn more about filtering.](/learn/filtering_and_sorting/filter_search_results)
### 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](/reference/api/settings#update-settings) or the [update pagination settings endpoint](/reference/api/settings#update-pagination-settings).
[Learn more about pagination.](/guides/front_end/pagination)
### 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](/reference/api/settings#update-settings) or the [update ranking rules endpoint](/reference/api/settings#update-ranking-rules).
[Learn more about ranking rules.](/learn/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](/reference/api/settings#update-settings) or the [update sortable attributes endpoint](/reference/api/settings#update-sortable-attributes). You can then use the [`sort` search parameter](/reference/api/search#sort) to sort your results in ascending or descending order.
[Learn more about sorting.](/learn/filtering_and_sorting/sort_search_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](/reference/api/settings#stop-words) and Meilisearch will ignore them during search.
Change your index's stop words list using the [update settings endpoint](/reference/api/settings#update-settings) or the [update stop words endpoint](/reference/api/settings#update-stop-words). In addition to improving relevancy, designating common words as stop words greatly improves performance.
[Learn more about stop words.](/reference/api/settings#stop-words)
### 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](/reference/api/settings#update-settings) or the [update synonyms endpoint](/reference/api/settings#update-synonyms).
[Learn more about synonyms.](/learn/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](/reference/api/settings#update-settings) or the [update typo tolerance endpoint](/reference/api/settings#update-typo-tolerance-settings).
[Learn more about typo tolerance.](/learn/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](/reference/api/indexes#swap-indexes).
# Primary key
Source: https://www.meilisearch.com/docs/learn/getting_started/primary_key
The primary key is a special field that must be present in all documents indexed by Meilisearch.
## Primary field
An [index](/learn/getting_started/indexes) in Meilisearch is a collection of [documents](/learn/getting_started/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
[
{
"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
{
"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
"id": "_Aabc012_"
```
Bad:
```json
"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
curl \
-X POST 'MEILISEARCH_URL/indexes' \
-H 'Content-Type: application/json' \
--data-binary '{
"uid": "books",
"primaryKey": "reference_number"
}'
```
```javascript JS
client.createIndex('books', { primaryKey: 'reference_number' })
```
```python Python
client.create_index('books', {'primaryKey': 'reference_number'})
```
```php PHP
$client->createIndex('books', ['primaryKey' => 'reference_number']);
```
```java Java
client.createIndex("books", "reference_number");
```
```ruby Ruby
client.create_index('books', primary_key: 'reference_number')
```
```go Go
client.CreateIndex(&meilisearch.IndexConfig{
Uid: "books",
PrimaryKey: "reference_number",
})
```
```csharp C#
TaskInfo task = await client.CreateIndexAsync("books", "reference_number");
```
```rust Rust
client
.create_index("books", Some("reference_number"))
.await
.unwrap();
```
```swift Swift
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
await client.createIndex('books', primaryKey: 'reference_number');
```
```json
{
"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
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
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
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
$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
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
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
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#
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
#[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
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
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
{
"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](/reference/api/documents#delete-all-documents) from that index, [change the primary key](/reference/api/indexes#update-an-index), then [add them](/reference/api/documents#add-or-replace-documents) again.
The code below updates the primary key to `title`:
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/indexes/books' \
-H 'Content-Type: application/json' \
--data-binary '{ "primaryKey": "title" }'
```
```javascript JS
client.updateIndex('books', {
primaryKey: 'title'
})
```
```python Python
client.index('books').update(primary_key='title')
```
```php PHP
$client->updateIndex('books', ['primaryKey' => 'title']);
```
```java Java
client.updateIndex("books", "title");
```
```ruby Ruby
client.index('books').update(primary_key: 'title')
```
```go Go
client.Index("books").UpdateIndex(&meilisearch.UpdateIndexRequestParams{
PrimaryKey: "title",
})
```
```csharp C#
TaskInfo task = await client.UpdateIndexAsync("books", "title");
```
```rust Rust
let task = IndexUpdater::new("books", &client)
.with_primary_key("title")
.execute()
.await
.unwrap();
```
```swift Swift
client.updateIndex(uid: "movies", primaryKey: "title") { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.updateIndex('books', 'title');
```
**Response:**
```json
{
"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
{
"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
{
"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
{
"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
{
"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"
}
```
# Search preview
Source: https://www.meilisearch.com/docs/learn/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](/learn/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](/learn/filtering_and_sorting/sort_search_results) and [filtering](/learn/filtering_and_sorting/filter_search_results). 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](/learn/ai_powered_search/getting_started_with_ai_search) 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:
# What is Meilisearch?
Source: https://www.meilisearch.com/docs/learn/getting_started/what_is_meilisearch
Meilisearch is a search engine featuring a blazing fast RESTful search API, typo tolerance, comprehensive language support, and much more.
Meilisearch is a **RESTful search API**. It aims to be a **ready-to-go solution** for everyone who wants a **fast and relevant search experience** for their end-users ⚡️🔎
## Meilisearch Cloud
[Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=what-is-meilisearch) is the recommended way of using Meilisearch. Using Meilisearch Cloud greatly simplifies installing, maintaining, and updating Meilisearch. [Get started with a 14-day free trial](https://www.meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=what-is-meilisearch).
## Demo
[](https://where2watch.meilisearch.com/?utm_campaign=oss\&utm_source=docs\&utm_medium=what-is-meilisearch\&utm_content=gif)
*Meilisearch helps you find where to watch a movie at [where2watch.meilisearch.com](https://where2watch.meilisearch.com/?utm_campaign=oss\&utm_source=docs\&utm_medium=what-is-meilisearch\&utm_content=link).*
## Features
* **Blazing fast**: Answers in less than 50 milliseconds
* [AI-powered search](/learn/ai_powered_search/getting_started_with_ai_search): Use the power of AI to make search feel human
* **Search as you type**: Results are updated on each keystroke using [prefix-search](/learn/engine/prefix#prefix-search)
* [Typo tolerance](/learn/relevancy/typo_tolerance_settings): Get relevant matches even when queries contain typos and misspellings
* [Comprehensive language support](/learn/resources/language): Optimized support for **Chinese, Japanese, Hebrew, and languages using the Latin alphabet**
* **Returns the whole document**: The entire document is returned upon search
* **Highly customizable search and indexing**: Customize search behavior to better meet your needs
* [Custom ranking](/learn/relevancy/relevancy): Customize the relevancy of the search engine and the ranking of the search results
* [Filtering](/learn/filtering_and_sorting/filter_search_results) and [faceted search](/learn/filtering_and_sorting/search_with_facet_filters): Enhance user search experience with custom filters and build a faceted search interface in a few lines of code
* [Highlighting](/reference/api/search#highlight-tags): Highlighted search results in documents
* [Stop words](/reference/api/settings#stop-words): Ignore common non-relevant words like `of` or `the`
* [Synonyms](/reference/api/settings#synonyms): Configure synonyms to include more relevant content in your search results
* **RESTful API**: Integrate Meilisearch in your technical stack with our plugins and SDKs
* [Search preview](/learn/getting_started/search_preview): Allows you to test your search settings without implementing a front-end
* [API key management](/learn/security/basic_security): Protect your instance with API keys. Set expiration dates and control access to indexes and endpoints so that your data is always safe
* [Multitenancy and tenant tokens](/learn/security/multitenancy_tenant_tokens): Manage complex multi-user applications. Tenant tokens help you decide which documents each one of your users can search
* [Multi-search](/reference/api/multi_search): Perform multiple search queries on multiple indexes with a single HTTP request
* [Geosearch](/learn/filtering_and_sorting/geosearch): Filter and sort results based on their geographic location
* [Index swapping](/learn/getting_started/indexes#swapping-indexes): Deploy major database updates with zero search downtime
## Philosophy
Our goal is to provide a simple and intuitive experience for both developers and end-users. Ease of use was the primary focus of Meilisearch from its first release, and it continues to drive its development today.
Meilisearch's ease-of-use goes hand-in-hand with ultra relevant search results. Meilisearch **sorts results according to a set of [ranking rules](/learn/relevancy/ranking_rules)**. Our default ranking rules work for most use cases as we developed them by working directly with our users. You can also **configure the [search parameters](/reference/api/search)** to refine your search even further.
Meilisearch should **not be your main data store**. It is a search engine, not a database. Meilisearch should contain only the data you want your users to search through. If you must add data that is irrelevant to search, be sure to [make those fields non-searchable](/learn/relevancy/displayed_searchable_attributes#searchable-fields) to improve relevancy and response time.
Meilisearch provides an intuitive search-as-you-type experience with response times under 50 milliseconds, no matter whether you are developing a site or an app. This helps end-users find what they are looking for quickly and efficiently. To make that happen, we are fully committed to the philosophy of [prefix search](/learn/engine/prefix).
## Give it a try
Instead of showing you examples, why not just invite you to test Meilisearch interactively in the **out-of-the-box search preview** we deliver?
There's no need to write a single line of front-end code. All you need to do is follow [this guide](/learn/self_hosted/getting_started_with_self_hosted_meilisearch) to give the search engine a try!
# Indexing best practices
Source: https://www.meilisearch.com/docs/learn/indexing/indexing_best_practices
Tips to speed up your documents indexing process.
In this guide, you will find some of the best practices to index your data efficiently and speed up the indexing process.
## Define searchable attributes
Review your list of [searchable attributes](/learn/relevancy/displayed_searchable_attributes#searchable-fields) and ensure it includes only the fields you want to be checked for query word matches. This improves both relevancy and search speed by removing irrelevant data from your database. It will also keep your disk usage to the necessary minimum.
By default, all document fields are searchable. The fewer fields Meilisearch needs to index, the faster the indexing process.
### Review filterable and sortable attributes
Some document fields are necessary for [filtering](/learn/filtering_and_sorting/filter_search_results) and [sorting](/learn/filtering_and_sorting/sort_search_results) results, but they do not need to be *searchable*. Generally, **numeric and boolean fields** fall into this category. Make sure to review your list of searchable attributes and remove any fields that are only used for filtering or sorting.
## Configure your index before adding documents
When creating a new index, first [configure its settings](/reference/api/settings) and only then add your documents. Whenever you update settings such as [ranking rules](/learn/relevancy/relevancy), Meilisearch will trigger a reindexing of all your documents. This can be a time-consuming process, especially if you have a large dataset. For this reason, it is better to define ranking rules and other settings before indexing your data.
## Optimize document size
Smaller documents are processed faster, so make sure to trim down any unnecessary data from your documents. When a document field is missing from the list of [searchable](/reference/api/settings#searchable-attributes), [filterable](/reference/api/settings#filterable-attributes), [sortable](/reference/api/settings#sortable-attributes), or [displayed](/reference/api/settings#displayed-attributes) attributes, it might be best to remove it from the document. To go further, consider compressing your data using methods such as `br`, `deflate`, or `gzip`. Consult the [supported encoding formats reference](/reference/api/overview#content-encoding).
## Prefer bigger HTTP payloads
A single large HTTP payload is processed more quickly than multiple smaller payloads. For example, adding the same 100,000 documents in two batches of 50,000 documents will be quicker than adding them in four batches of 25,000 documents. By default, Meilisearch sets the maximum payload size to 100MB, but [you can change this value if necessary](/learn/self_hosted/configure_meilisearch_at_launch#payload-limit-size).
Larger payload consume more RAM. An instance may crash if it requires more memory than is currently available in a machine.
## Keep Meilisearch up-to-date
Make sure to keep your Meilisearch instance up-to-date to benefit from the latest improvements. You can see [a list of all our engine releases on GitHub](https://github.com/meilisearch/meilisearch/releases?q=prerelease%3Afalse).
For more information on how indexing works under the hood, take a look [this blog post about indexing best practices](https://blog.meilisearch.com/best-practices-for-faster-indexing/).
## Do not use Meilisearch as your main database
Meilisearch is optimized for information retrieval was not designed to be your main data container. The more documents you add, the longer will indexing and search take. Only index documents you want to retrieve when searching.
## Create separate indexes for multiple languages
If you have a multilingual dataset, create a separate index for each language.
## Remove I/O operation limits
Ensure there is no limit to I/O operations in your machine. The restrictions imposed by cloud providers such as [AWS's Amazon EBS service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html#IOcredit) can severely impact indexing performance.
## Consider upgrading to machines with SSDs, more RAM, and multi-threaded processors
If you have followed the previous tips in this guide and are still experiencing slow indexing times, consider upgrading your machine.
Indexing is a memory-intensive and multi-threaded operation. The more memory and processor cores available, the faster Meilisearch will index new documents. When trying to improve indexing speed, using a machine with more processor cores is more effective than increasing RAM.
Due to how Meilisearch works, it is best to avoid HDDs (Hard Disk Drives) as they can easily become performance bottlenecks.
## Enable binary quantization when using AI-powered search
If you are experiencing performance issues when indexing documents for AI-powered search, consider enabling [binary quantization](/reference/api/settings#binaryquantized) for your embedders. Binary quantization compresses vectors by representing each dimension with 1-bit values. This reduces the relevancy of semantic search results, but greatly improves performance.
Binary quantization works best with large datasets containing more than 1M documents and using models with more than 1400 dimensions.
**Activating binary quantization is irreversible.** Once enabled, Meilisearch converts all vectors and discards all vector data that does fit within 1-bit. The only way to recover the vectors' original values is to re-vectorize the whole index in a new embedder.
# Impact of RAM and multi-threading on indexing performance
Source: https://www.meilisearch.com/docs/learn/indexing/ram_multithreading_performance
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`](/learn/self_hosted/configure_meilisearch_at_launch#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`](/learn/self_hosted/configure_meilisearch_at_launch#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.
Meilisearch is aware of this issue and actively trying to resolve it. If you are struggling with memory-related crashes, consider:
* Adding new documents in smaller batches
* Increasing your machine's RAM
* [Following indexing best practices](/learn/indexing/indexing_best_practices)
# Rename an index
Source: https://www.meilisearch.com/docs/learn/indexing/rename_an_index
Use the PATCH endpoint of the /indexes route to rename an index
This guide shows you how to change the name of an index.
## Requirements
* A Meilisearch project with at least one index
* A command-line terminal
## Choose the target index and its new name
Decide which index you want to rename and keep note of its `uid`. This guide changes the name of an index called `INDEX_A`.
Also choose the new name you wish to assign the index. This guide uses `INDEX_B` for the new name of the index.
## Query the `/indexes/{index_uid}` route
Send a `PATCH` request targeting the index you want to rename:
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/indexes/INDEX_A' \
-H 'Content-Type: application/json' \
--data-binary '{ "uid": "INDEX_B" }'
```
Replace `INDEX_A` with the current name of your index, and `INDEX_B` with its new name.
# Tokenization
Source: https://www.meilisearch.com/docs/learn/indexing/tokenization
Tokenization is the process of taking a sentence or phrase and splitting it into smaller units of language. It is a crucial procedure when indexing documents.
**Tokenization** is the act of taking a sentence or phrase and splitting it into smaller units of language, called tokens. It is the first step of document indexing in the Meilisearch engine, and is a critical factor in the quality of search results.
Breaking sentences into smaller chunks requires understanding where one word ends and another begins, making tokenization a highly complex and language-dependent task. Meilisearch's solution to this problem is a **modular tokenizer** that follows different processes, called **pipelines**, based on the language it detects.
This allows Meilisearch to function in several different languages with zero setup.
## Deep dive: The Meilisearch tokenizer
When you add documents to a Meilisearch index, the tokenization process is handled by an abstract interface called the tokenizer. The tokenizer is responsible for splitting each field by writing system (for example, Latin alphabet, Chinese hanzi). It then applies the corresponding pipeline to each part of each document field.
We can break down the tokenization process like so:
1. Crawl the document(s), splitting each field by script
2. Go back over the documents part-by-part, running the corresponding tokenization pipeline, if it exists
Pipelines include many language-specific operations. Currently, we have a number of pipelines, including a default pipeline for languages that use whitespace to separate words, and dedicated pipelines for Chinese, Japanese, Hebrew, Thai, and Khmer.
For more details, check out the [tokenizer contribution guide](https://github.com/meilisearch/charabia).
# Implement sharding with remote federated search
Source: https://www.meilisearch.com/docs/learn/multi_search/implement_sharding
This guide walks you through implementing a sharding strategy by activating the `/network` route, configuring the network object, and performing remote federated searches.
export const NoticeTag = ({label}) =>
{label}
;
Sharding is the process of splitting an index containing many documents into multiple smaller indexes, often called shards. This horizontal scaling technique is useful when handling large databases. In Meilisearch, the best way to implement a sharding strategy is to use remote federated search.
This guide walks you through activating the `/network` route, configuring the network object, and performing remote federated searches.
Sharding is an Enterprise Edition feature. You are free to use it for evaluation purposes. Please [reach out to us](mailto:sales@meilisearch.com) before using it in production.
## Configuring multiple instances
To minimize issues and limit unexpected behavior, instance, network, and index configuration should be identical for all shards. This guide describes the individual steps you must take on a single instance and assumes you will replicate them across all instances.
## Prerequisites
* Multiple Meilisearch projects (instances) running Meilisearch >=v1.19
## Activate the `/network` endpoint
### Meilisearch Cloud
If you are using Meilisearch Cloud, contact support to enable this feature in your projects.
### Self-hosting
Use the `/experimental-features` route to enable `network`:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{
"network": true
}'
```
Meilisearch should respond immediately, confirming the route is now accessible. Repeat this process for all instances.
## Configuring the network object
Next, you must configure the network object. It consists of the following fields:
* `remotes`: defines a list with the required information to access each remote instance
* `self`: specifies which of the configured `remotes` corresponds to the current instance
* `sharding`: whether to use sharding.
### Setting up the list of remotes
Use the `/network` route to configure the `remotes` field of the network object. `remotes` should be an object containing one or more objects. Each one of the nested objects should consist of the name of each instance, associated with its URL and an API key with search permission:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/network' \
-H 'Content-Type: application/json' \
--data-binary '{
"remotes": {
"REMOTE_NAME_1": {
"url": "INSTANCE_URL_1",
"searchApiKey": "SEARCH_API_KEY_1"
},
"REMOTE_NAME_2": {
"url": "INSTANCE_URL_2",
"searchApiKey": "SEARCH_API_KEY_2"
},
"REMOTE_NAME_3": {
"url": "INSTANCE_URL_3",
"searchApiKey": "SEARCH_API_KEY_3"
},
…
}
}'
```
Configure the entire set of remote instances in your sharded database, making sure to send the same remotes to each instance.
### Specify the name of the current instance
Now all instances share the same list of remotes, set the `self` field to specify which of the remotes corresponds to the current instance:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/network' \
-H 'Content-Type: application/json' \
--data-binary '{
"self": "REMOTE_NAME_1"
}'
```
Meilisearch processes searches on the remote that corresponds to `self` locally instead of making a remote request.
### Enabling sharding
Finally enable the automatic sharding of documents by Meilisearch on all instances:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/network' \
-H 'Content-Type: application/json' \
--data-binary '{
"sharding": true
}'
```
### Adding or removing an instance
Changing the topology of the network involves moving some documents from an instance to another, depending on your hashing scheme.
As Meilisearch does not provide atomicity across multiple instances, you will need to either:
1. accept search downtime while migrating documents
2. accept some documents will not appear in search results during the migration
3. accept some duplicate documents may appear in search results during the migration
#### Reducing downtime
If your disk space allows, you can reduce the downtime by applying the following algorithm:
1. Create a new temporary index in each remote instance
2. Compute the new instance for each document
3. Send the documents to the temporary index of their new instance
4. Once Meilisearch has copied all documents to their instance of destination, swap the new index with the previously used index
5. Delete the temporary index after the swap
6. Update network configuration and search queries across all instances
## Create indexes
Create the same empty indexes with the same settings on all instances.
Keeping the settings and indexes in sync is important to avoid errors and unexpected behavior, though not strictly required.
## Add documents
Pick a single instance to send all your documents to. Documents will be replicated to the other instances.
Each instance will index the documents they are responsible for and ignore the others.
You *may* send send the same document to multiple instances, the task will be replicated to all instances, and only the instance responsible for the document will index it.
Similarly, you may send any future versions of any document to the instance you picked, and only the correct instance will process that document.
### Updating index settings
Changing settings in a sharded database is not fundamentally different from changing settings on a single Meilisearch instance. If the update enables a feature, such as setting filterable attributes, wait until all changes have been processed before using the `filter` search parameter in a query. Likewise, if an update disables a feature, first remove it from your search requests, then update your settings.
## Perform a search
Send your federated search request containing one query per instance:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/multi-search' \
-H 'Content-Type: application/json' \
--data-binary '{
"federation": {},
"queries": [
{
"indexUid": "movies",
"q": "batman",
"federationOptions": {
"remote": "ms-00"
}
},
{
"indexUid": "movies",
"q": "batman",
"federationOptions": {
"remote": "ms-01"
}
}
]
}'
```
If all instances share the same network configuration, you can send the search request to any instance. Having `"remote": "ms-00"` appear in the list of queries on the instance of that name will not cause an actual proxy search thanks to `network.self`.
# Differences between multi-search and federated search
Source: https://www.meilisearch.com/docs/learn/multi_search/multi_search_vs_federated_search
This article defines multi-search and federated search and then describes the different uses of each.
This article defines multi-search and federated search and then describes the different uses of each.
## What is multi-search?
Multi-search, also called multi-index search, is a search operation that makes multiple queries at the same time. These queries may target different indexes. Meilisearch then returns a separate list results for each query. Use the `/multi-search` route to perform multi-searches.
Multi-search favors discovery scenarios, where users might not have a clear idea of what they need and searches might have many valid results.
## What is federated search?
Federated search is a type of multi-index search. This operation also makes multiple search requests at the same time, but returns a single list with the most relevant results from all queries. Use the `/multi-search` route and specify a non-null value for `federation` to perform a federated search.
Federated search favors scenarios where users have a clear idea of what they need and expect a single best top result.
## Use cases
Because multi-search groups results by query, it is often useful when the origin and type of document contain information relevant to your users. For example, a person searching for `shygirl` in a music streaming application is likely to appreciate seeing separate results for matching artists, albums, and individual tracks.
Federated search is a better approach when the source of the information is not relevant to your users. For example, a person searching for a client's email in a CRM application is unlikely to care whether this email comes from chat logs, support tickets, or other data sources.
# Using multi-search to perform a federated search
Source: https://www.meilisearch.com/docs/learn/multi_search/performing_federated_search
In this tutorial you will see how to perform a query searching multiple indexes at the same time to obtain a single list of results.
Meilisearch allows you to make multiple search requests at the same time with the `/multi-search` endpoint. A federated search is a multi-search that returns results from multiple queries in a single list.
In this tutorial you will see how to create separate indexes containing different types of data from a CRM application. You will then perform a query searching all these indexes at the same time to obtain a single list of results.
## Requirements
* A running Meilisearch project
* A command-line console
## Create three indexes
Download the following datasets: `crm-chats.json`, `crm-profiles.json`, and `crm-tickets.json` containing data from a fictional CRM application.
Add the datasets to Meilisearch and create three separate indexes, `profiles`, `chats`, and `tickets`:
```sh
curl -X POST 'MEILISEARCH_URL/indexes/profiles' -H 'Content-Type: application/json' --data-binary @crm-profiles.json &&
curl -X POST 'MEILISEARCH_URL/indexes/chats' -H 'Content-Type: application/json' --data-binary @crm-chats.json &&
curl -X POST 'MEILISEARCH_URL/indexes/tickets' -H 'Content-Type: application/json' --data-binary @crm-tickets.json
```
[Use the tasks endpoint](/learn/async/working_with_tasks) to check the indexing status. Once Meilisearch successfully indexed all three datasets, you are ready to perform a federated search.
## Perform a federated search
When you are looking for Natasha Nguyen's email address in your CRM application, you may not know whether you will find it in a chat log, among the existing customer profiles, or in a recent support ticket. In this situation, you can use federated search to search across all possible sources and receive a single list of results.
Use the `/multi-search` endpoint with the `federation` parameter to query the three indexes simultaneously:
```sh
curl \
-X POST 'MEILISEARCH_URL/multi-search' \
-H 'Content-Type: application/json' \
--data-binary '{
"federation": {},
"queries": [
{
"indexUid": "chats",
"q": "natasha"
},
{
"indexUid": "profiles",
"q": "natasha"
},
{
"indexUid": "tickets",
"q": "natasha"
}
]
}'
```
Meilisearch should respond with a single list of search results:
```json
{
"hits": [
{
"id": 0,
"client_name": "Natasha Nguyen",
"message": "My email is natasha.nguyen@example.com",
"time": 1727349362,
"_federation": {
"indexUid": "chats",
"queriesPosition": 0
}
},
…
],
"processingTimeMs": 0,
"limit": 20,
"offset": 0,
"estimatedTotalHits": 3,
"semanticHitCount": 0
}
```
## Promote results from a specific index
Since this is a CRM application, users have profiles with their preferred contact information. If you want to search for Riccardo Rotondo's preferred email, you can boost documents in the `profiles` index.
Use the `weight` property of the `federation` parameter to boost results coming from a specific query:
```sh
curl \
-X POST 'MEILISEARCH_URL/multi-search' \
-H 'Content-Type: application/json' \
--data-binary '{
"federation": {},
"queries": [
{
"indexUid": "chats",
"q": "rotondo"
},
{
"indexUid": "profiles",
"q": "rotondo",
"federationOptions": {
"weight": 1.2
}
},
{
"indexUid": "tickets",
"q": "rotondo"
}
]
}'
```
This request will lead to results from the query targeting `profile` ranking higher than documents from other queries:
```json
{
"hits": [
{
"id": 1,
"name": "Riccardo Rotondo",
"email": "riccardo.rotondo@example.com",
"_federation": {
"indexUid": "profiles",
"queriesPosition": 1
}
},
…
],
"processingTimeMs": 0,
"limit": 20,
"offset": 0,
"estimatedTotalHits": 3,
"semanticHitCount": 0
}
```
## Conclusion
You have created three indexes, then performed a federated multi-index search to receive all results in a single list. You then used `weight` to boost results from the index most likely to contain the information you wanted.
# Attribute ranking order
Source: https://www.meilisearch.com/docs/learn/relevancy/attribute_ranking_order
This article explains how the order of attributes in the `searchableAttributes` array impacts search result relevancy.
In most datasets, some fields are more relevant to search than others. A `title`, for example, might be more meaningful to a movie search than its `overview` or its `release_date`.
When `searchableAttributes` is using its default value, `[*]`, all fields carry the same weight.
If you manually configure [the searchable attributes list](/learn/relevancy/displayed_searchable_attributes#the-searchableattributes-list), attributes that appear early in the array are more important when calculating search result relevancy.
## Example
```json
[
"title",
"overview",
"release_date"
]
```
With the above attribute ranking order, matching words found in the `title` field would have a higher impact on relevancy than the same words found in `overview` or `release_date`. If you searched for "1984", for example, results like Michael Radford's film "1984" would be ranked higher than movies released in the year 1984.
## Attribute ranking order and nested objects
By default, nested fields share the same weight as their parent attribute. Use dot notation to set different weights for attributes in nested objects:
```json
[
"title",
"review.critic",
"overview",
"review.user"
]
```
With the above ranking order, `review.critic` becomes more important than its sibling `review.user` when calculating a document's ranking score.
The `attribute` rule's position in [`rankingRules`](/learn/relevancy/ranking_rules) determines how the results are sorted. Meaning, **if `attribute` is at the bottom of the ranking rules list, it will have almost no impact on your search results.**
# Custom ranking rules
Source: https://www.meilisearch.com/docs/learn/relevancy/custom_ranking_rules
Custom ranking rules promote certain documents over other search results that are otherwise equally relevant.
There are two types of ranking rules in Meilisearch: [built-in ranking rules](/learn/relevancy/ranking_rules) and custom ranking rules. This article describes the main aspects of using and configuring custom ranking rules.
Custom ranking rules promote certain documents over other search results that are otherwise equally relevant.
## Ascending and descending sorting rules
Meilisearch supports two types of custom rules: one for ascending sort and one for descending sort.
To add a custom ranking rule, you have to communicate the attribute name followed by a colon (`:`) and either `asc` for ascending order or `desc` for descending order.
* To apply an **ascending sort** (results sorted by increasing value of the attribute): `attribute_name:asc`
* To apply a **descending sort** (results sorted by decreasing value of the attribute): `attribute_name:desc`
**The attribute must have either a numeric or a string value** in all of the documents contained in that index.
You can add this rule to the existing list of ranking rules using the [update settings endpoint](/reference/api/settings#update-settings) or [update ranking rules endpoint](/reference/api/settings#update-ranking-rules).
## Example
Suppose you have a movie dataset. The documents contain the fields `release_date` with a timestamp as value, and `movie_ranking`, an integer that represents its ranking.
The following example creates a rule that makes older movies more relevant than recent ones. A movie released in 1999 will appear before a movie released in 2020.
```
release_date:asc
```
The following example will create a rule that makes movies with a good rank more relevant than movies with a lower rank. Movies with a higher ranking will appear first.
```
movie_ranking:desc
```
The following array includes all built-in ranking rules and places the custom rules at the bottom of the processing order:
```json
[
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"release_date:asc",
"movie_ranking:desc"
]
```
## Sorting at search time and custom ranking rules
Meilisearch allows users to define [sorting order at query time](/learn/filtering_and_sorting/sort_search_results) by using the [`sort` search parameter](/reference/api/search#sort). There is some overlap between sorting and custom ranking rules, but the two do have different uses.
In general, `sort` will be most useful when you want to allow users to define what type of results they want to see first. A good use-case for `sort` is creating a webshop interface where customers can sort products by descending or ascending product price.
Custom ranking rules, instead, are always active once configured and are useful when you want to promote certain types of results. A good use-case for custom ranking rules is ensuring discounted products in a webshop always feature among the top results.
Meilisearch does not offer native support for promoting, pinning, and boosting specific documents so they are displayed more prominently than other search results. Consult these Meilisearch blog articles for workarounds on [implementing promoted search results with React InstantSearch](https://blog.meilisearch.com/promoted-search-results-with-react-instantsearch) and [document boosting](https://blog.meilisearch.com/document-boosting).
# Displayed and searchable attributes
Source: https://www.meilisearch.com/docs/learn/relevancy/displayed_searchable_attributes
Displayed and searchable attributes define what data Meilisearch returns after a successful query and which fields Meilisearch takes in account when searching. Knowing how to configure them can help improve your application's performance.
By default, whenever a document is added to Meilisearch, all new attributes found in it are automatically added to two lists:
* [`displayedAttributes`](/learn/relevancy/displayed_searchable_attributes#displayed-fields): Attributes whose fields are displayed in documents
* [`searchableAttributes`](/learn/relevancy/displayed_searchable_attributes#the-searchableattributes-list): Attributes whose values are searched for matching query words
By default, every field in a document is **displayed** and **searchable**. These properties can be modified in the [settings](/reference/api/settings).
## Displayed fields
The fields whose attributes are added to the [`displayedAttributes` list](/reference/api/settings#displayed-attributes) are **displayed in each matching document**.
Documents returned upon search contain only displayed fields. If a field attribute is not in the displayed-attribute list, the field won't be added to the returned documents.
**By default, all field attributes are set as displayed**.
### Example
Suppose you manage a database that contains information about movies. By adding the following settings, documents returned upon search will contain the fields `title`, `overview`, `release_date` and `genres`.
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/movies/settings/displayed-attributes' \
-H 'Content-Type: application/json' \
--data-binary '[
"title",
"overview",
"genres",
"release_date"
]'
```
```javascript JS
client.index('movies').updateDisplayedAttributes([
'title',
'overview',
'genres',
'release_date',
]
)
```
```python Python
client.index('movies').update_displayed_attributes([
'title',
'overview',
'genres',
'release_date'
])
```
```php PHP
$client->index('movies')->updateDisplayedAttributes([
'title',
'overview',
'genres',
'release_date'
]);
```
```java Java
String[] attributes = {"title", "overview", "genres", "release_date"}
client.index("movies").updateDisplayedAttributesSettings(attributes);
```
```ruby Ruby
client.index('movies').update_settings({
displayed_attributes: [
'title',
'overview',
'genres',
'release_date'
]
})
```
```go Go
displayedAttributes := []string{
"title",
"overview",
"genres",
"release_date",
}
client.Index("movies").UpdateDisplayedAttributes(&displayedAttributes)
```
```csharp C#
await client.Index("movies").UpdateDisplayedAttributesAsync(new[]
{
"title",
"overview",
"genres",
"release_date"
});
```
```rust Rust
let displayed_attributes = [
"title",
"overvieww",
"genres",
"release_date"
];
let task: TaskInfo = client
.index("movies")
.set_displayed_attributes(&displayed_attributes)
.await
.unwrap();
```
```swift Swift
let displayedAttributes: [String] = [
"title",
"overview",
"genres",
"release_date"
]
client.index("movies").updateDisplayedAttributes(displayedAttributes) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').updateDisplayedAttributes([
'title',
'overview',
'genres',
'release_date',
]);
```
## Searchable fields
A field can either be **searchable** or **non-searchable**.
When you perform a search, all searchable fields are checked for matching query words and used to assess document relevancy, while non-searchable fields are ignored entirely. **By default, all fields are searchable.**
Non-searchable fields are most useful for internal information that's not relevant to the search experience, such as URLs, sales numbers, or ratings used exclusively for sorting results.
Even if you make a field non-searchable, it will remain [stored in the database](#data-storing) and can be made searchable again at a later time.
### The `searchableAttributes` list
Meilisearch uses an ordered list to determine which attributes are searchable. The order in which attributes appear in this list also determines their [impact on relevancy](/learn/relevancy/attribute_ranking_order), from most impactful to least.
In other words, the `searchableAttributes` list serves two purposes:
1. It designates the fields that are searchable
2. It dictates the [attribute ranking order](/learn/relevancy/attribute_ranking_order)
There are two possible modes for the `searchableAttributes` list.
#### Default: Automatic
**By default, all attributes are automatically added to the `searchableAttributes` list in their order of appearance.** This means that the initial order will be based on the order of attributes in the first document indexed, with each new attribute found in subsequent documents added at the end of this list.
This default behavior is indicated by a `searchableAttributes` value of `["*"]`. To verify the current value of your `searchableAttributes` list, use the [get searchable attributes endpoint](/reference/api/settings#get-searchable-attributes).
If you'd like to restore your searchable attributes list to this default behavior, [set `searchableAttributes` to an empty array `[]`](/reference/api/settings#update-searchable-attributes) or use the [reset searchable attributes endpoint](/reference/api/settings#reset-searchable-attributes).
#### Manual
You may want to make some attributes non-searchable, or change the [attribute ranking order](/learn/relevancy/attribute_ranking_order) after documents have been indexed. To do so, place the attributes in the desired order and send the updated list using the [update searchable attributes endpoint](/reference/api/settings#update-searchable-attributes).
After manually updating the `searchableAttributes` list, **subsequent new attributes will no longer be automatically added** unless the settings are [reset](/reference/api/settings#reset-searchable-attributes).
Due to an implementation bug, manually updating `searchableAttributes` will change the displayed order of document fields in the JSON response. This behavior is inconsistent and will be fixed in a future release.
#### Example
Suppose that you manage a database of movies with the following fields: `id`, `overview`, `genres`, `title`, `release_date`. These fields all contain useful information. However, **some are more useful to search than others**. To make the `id` and `release_date` fields non-searchable and re-order the remaining fields by importance, you might update the searchable attributes list in the following way.
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/movies/settings/searchable-attributes' \
-H 'Content-Type: application/json' \
--data-binary '[
"title",
"overview",
"genres"
]'
```
```javascript JS
client.index('movies').updateSearchableAttributes([
'title',
'overview',
'genres',
]
)
```
```python Python
client.index('movies').update_searchable_attributes([
'title',
'overview',
'genres'
])
```
```php PHP
$client->index('movies')->updateSearchableAttributes([
'title',
'overview',
'genres'
]);
```
```java Java
String[] attributes = {"title", "overview", "genres"}
client.index("movies").updateSearchableAttributesSettings(attributes);
```
```ruby Ruby
client.index('movies').update_searchable_attributes([
'title',
'overview',
'genres'
])
```
```go Go
searchableAttributes := []string{
"title",
"overview",
"genres",
}
client.Index("movies").UpdateSearchableAttributes(&searchableAttributes)
```
```csharp C#
await client.Index("movies").UpdateSearchableAttributesAsync(new[]
{
"title",
"overview",
"genres"
});
```
```rust Rust
let searchable_attributes = [
"title",
"overvieww",
"genres"
];
let task: TaskInfo = client
.index("movies")
.set_searchable_attributes(&searchable_attributes)
.await
.unwrap();
```
```swift Swift
let searchableAttributes: [String] = [
"title",
"overview",
"genres"
]
client.index("movies").updateSearchableAttributes(searchableAttributes) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client
.index('movies')
.updateSearchableAttributes(['title', 'overview', 'genres']);
```
### Customizing attributes to search on at search time
By default, all queries search through all attributes in the `searchableAttributes` list. Use [the `attributesToSearchOn` search parameter](/reference/api/search#customize-attributes-to-search-on-at-search-time) to restrict specific queries to a subset of your index's `searchableAttributes`.
## Data storing
All fields are stored in the database. **This behavior cannot be changed**.
Thus, even if a field is missing from both the `displayedAttributes` list and the `searchableAttributes` list, **it is still stored in the database** and can be added to either or both lists at any time.
# Distinct attribute
Source: https://www.meilisearch.com/docs/learn/relevancy/distinct_attribute
Distinct attribute is a field that prevents Meilisearch from returning a set of several similar documents. Often used in ecommerce datasets where many documents are variations of the same item.
The distinct attribute is a special, user-designated field. It is most commonly used to prevent Meilisearch from returning a set of several similar documents, instead forcing it to return only one.
You may set a distinct attribute in two ways: using the `distinctAttribute` index setting during configuration, or the `distinct` search parameter at search time.
## Setting a distinct attribute during configuration
`distinctAttribute` is an index setting that configures a default distinct attribute Meilisearch applies to all searches and facet retrievals in that index.
There can be only one `distinctAttribute` per index. Trying to set multiple fields as a `distinctAttribute` will return an error.
The value of a field configured as a distinct attribute will always be unique among returned documents. This means **there will never be more than one occurrence of the same value** in the distinct attribute field among the returned documents.
When multiple documents have the same value for the distinct attribute, Meilisearch returns only the highest-ranked result after applying [ranking rules](/learn/relevancy/ranking_rules). If two or more documents are equivalent in terms of ranking, Meilisearch returns the first result according to its `internal_id`.
## Example
Suppose you have an e-commerce dataset. For an index that contains information about jackets, you may have several identical items with minor variations such as color or size.
As shown below, this dataset contains three documents representing different versions of a Lee jeans leather jacket. One of the jackets is brown, one is black, and the last one is blue.
```json
[
{
"id": 1,
"description": "Leather jacket",
"brand": "Lee jeans",
"color": "brown",
"product_id": "123456"
},
{
"id": 2,
"description": "Leather jacket",
"brand": "Lee jeans",
"color": "black",
"product_id": "123456"
},
{
"id": 3,
"description": "Leather jacket",
"brand": "Lee jeans",
"color": "blue",
"product_id": "123456"
}
]
```
By default, a search for `lee leather jacket` would return all three documents. This might not be desired, since displaying nearly identical variations of the same item can make results appear cluttered.
In this case, you may want to return only one document with the `product_id` corresponding to this Lee jeans leather jacket. To do so, you could set `product_id` as the `distinctAttribute`.
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/jackets/settings/distinct-attribute' \
-H 'Content-Type: application/json' \
--data-binary '"product_id"'
```
```javascript JS
client.index('jackets').updateDistinctAttribute('product_id')
```
```python Python
client.index('jackets').update_distinct_attribute('product_id')
```
```php PHP
$client->index('jackets')->updateDistinctAttribute('product_id');
```
```java Java
client.index("jackets").updateDistinctAttributeSettings("product_id");
```
```ruby Ruby
client.index('jackets').update_distinct_attribute('product_id')
```
```go Go
client.Index("jackets").UpdateDistinctAttribute("product_id")
```
```csharp C#
await client.Index("jackets").UpdateDistinctAttributeAsync("product_id");
```
```rust Rust
let task: TaskInfo = client
.index("jackets")
.set_distinct_attribute("product_id")
.await
.unwrap();
```
```swift Swift
client.index("jackets").updateDistinctAttribute("product_id") { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('jackets').updateDistinctAttribute('product_id');
```
By setting `distinctAttribute` to `product_id`, search requests **will never return more than one document with the same `product_id`**.
After setting the distinct attribute as shown above, querying for `lee leather jacket` would only return the first document found. The response would look like this:
```json
{
"hits": [
{
"id": 1,
"description": "Leather jacket",
"brand": "Lee jeans",
"color": "brown",
"product_id": "123456"
}
],
"offset": 0,
"limit": 20,
"estimatedTotalHits": 1,
"processingTimeMs": 0,
"query": "lee leather jacket"
}
```
For more in-depth information on distinct attribute, consult the [API reference](/reference/api/settings#distinct-attribute).
## Setting a distinct attribute at search time
`distinct` is a search parameter you may add to any search query. It allows you to selectively use distinct attributes depending on the context. `distinct` takes precedence over `distinctAttribute`.
To use an attribute with `distinct`, first add it to the `filterableAttributes` list:
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/products/settings/filterable-attributes' \
-H 'Content-Type: application/json' \
--data-binary '[
"product_id",
"sku",
"url"
]'
```
```javascript JS
client.index('products').updateFilterableAttributes(['product_id', 'sku', 'url'])
```
```python Python
client.index('products').update_filterable_attributes(['product_id', 'sku', 'url'])
```
```php PHP
$client->index('products')->updateFilterableAttributes(['product_id', 'sku', 'url']);
```
```java Java
Settings settings = new Settings();
settings.setFilterableAttributes(new String[] {"product_id", "SKU", "url"});
client.index("products").updateSettings(settings);
```
```ruby Ruby
client.index('products').update_filterable_attributes([
'product_id',
'sku',
'url'
])
```
```go Go
filterableAttributes := []interface{}{
"product_id",
"sku",
"url",
}
client.Index("products").UpdateFilterableAttributes(&filterableAttributes)
```
```csharp C#
List attributes = new() { "product_id", "sku", "url" };
TaskInfo result = await client.Index("products").UpdateFilterableAttributesAsync(attributes);
```
```rust Rust
let task: TaskInfo = client
.index("products")
.settings()
.set_filterable_attributes(["product_id", "sku", "url"])
.execute()
.await
.unwrap();
```
Then use `distinct` in a search query, specifying one of the configured attributes:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/products/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "white shirt",
"distinct": "sku"
}'
```
```javascript JS
client.index('products').search('white shirt', { distinct: 'sku' })
```
```python Python
client.index('products').search('white shirt', { distinct: 'sku' })
```
```php PHP
$client->index('products')->search('white shirt', [
'distinct' => 'sku'
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("white shirt").distinct("sku").build();
client.index("products").search(searchRequest);
```
```ruby Ruby
client.index('products').search('white shirt', {
distinct: 'sku'
})
```
```go Go
client.Index("products").Search("white shirt", &meilisearch.SearchRequest{
Distinct: "sku",
})
```
```csharp C#
var params = new SearchQuery()
{
Distinct = "sku"
};
await client.Index("products").SearchAsync("white shirt", params);
```
```rust Rust
let res = client
.index("products")
.search()
.with_query("white shirt")
.with_distinct("sku")
.execute()
.await
.unwrap();
```
# Built-in ranking rules
Source: https://www.meilisearch.com/docs/learn/relevancy/ranking_rules
Built-in ranking rules are the core of Meilisearch's relevancy calculations.
There are two types of ranking rules in Meilisearch: built-in ranking rules and [custom ranking rules](/learn/relevancy/custom_ranking_rules). This article describes the main aspects of using and configuring built-in ranking rules.
Built-in ranking rules are the core of Meilisearch's relevancy calculations.
## List of built-in ranking rules
Meilisearch contains six built-in ranking rules in the following order:
```json
[
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness"
]
```
Depending on your needs, you might want to change this order. To do so, use the [update settings endpoint](/reference/api/settings#update-settings) or [update ranking rules endpoint](/reference/api/settings#update-ranking-rules).
## 1. Words
Results are sorted by **decreasing number of matched query terms**. Returns documents that contain all query terms first.
The `words` rule works from right to left. Therefore, the order of the query string impacts the order of results.
For example, if someone were to search `batman dark knight`, the `words` rule would rank documents containing all three terms first, documents containing only `batman` and `dark` second, and documents containing only `batman` third.
## 2. Typo
Results are sorted by **increasing number of typos**. Returns documents that match query terms with fewer typos first.
## 3. Proximity
Results are sorted by **increasing distance between matched query terms**. Returns documents where query terms occur close together and in the same order as the query string first.
[It is possible to lower the precision of this ranking rule.](/reference/api/settings#proximity-precision) This may significantly improve indexing performance. In a minority of use cases, lowering precision may also lead to lower search relevancy for queries using multiple search terms.
## 4. Attribute
Results are sorted according to the **[attribute ranking order](/learn/relevancy/attribute_ranking_order)**. Returns documents that contain query terms in more important attributes first.
Also, note the documents with attributes containing the query words at the beginning of the attribute will be considered more relevant than documents containing the query words at the end of the attributes.
## 5. Sort
Results are sorted **according to parameters decided at query time**. When the `sort` ranking rule is in a higher position, sorting is exhaustive: results will be less relevant but follow the user-defined sorting order more closely. When `sort` is in a lower position, sorting is relevant: results will be very relevant but might not always follow the order defined by the user.
Differently from other ranking rules, sort is only active for queries containing the [`sort` search parameter](/reference/api/search#sort). If a search request does not contain `sort`, or if its value is invalid, this rule will be ignored.
## 6. Exactness
Results are sorted by **the similarity of the matched words with the query words**. Returns documents that contain exactly the same terms as the ones queried first.
## Examples
### Typo
* `vogli`: 0 typo
* `volli`: 1 typo
The `typo` rule sorts the results by increasing number of typos on matched query words.
### Proximity
The reason why `Creature` is listed before `Mississippi Grind` is because of the `proximity` rule. The smallest **distance** between the matching words in `creature` is smaller than the smallest **distance** between the matching words in `Mississippi Grind`.
The `proximity` rule sorts the results by increasing distance between matched query terms.
### Attribute
`If It's Tuesday, This must be Belgium` is the first document because the matched word `Belgium` is found in the `title` attribute and not the `overview`.
The `attribute` rule sorts the results by [attribute importance](/learn/relevancy/attribute_ranking_order).
### Exactness
`Knight Moves` is displayed before `Knights of Badassdom`. `Knight` is exactly the same as the search query `Knight` whereas there is a letter of difference between `Knights` and the search query `Knight`.
# Ranking score
Source: https://www.meilisearch.com/docs/learn/relevancy/ranking_score
This article explains how the order of attributes in the `searchableAttributes` array impacts search result relevancy.
When using the [`showRankingScore` search parameter](/reference/api/search#ranking-score), Meilisearch adds a global ranking score field, `_rankingScore`, to each document. The `_rankingScore` is between `0.0` and `1.0`. The higher the ranking score, the more relevant the document.
Ranking rules sort documents either by relevancy (`words`, `typo`, `proximity`, `exactness`, `attribute`) or by the value of a field (`sort`). Since `sort` doesn't rank documents by relevancy, it does not influence the `_rankingScore`.
A document's ranking score does not change based on the scores of other documents in the same index.
For example, if a document A has a score of `0.5` for a query term, this value remains constant no matter the score of documents B, C, or D.
The table below details all the index settings that can influence the `_rankingScore`. **Unlisted settings do not influence the ranking score.**
| Index setting | Influences if | Rationale |
| :--------------------- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `searchableAttributes` | The `attribute` ranking rule is used | The `attribute` ranking rule rates the document depending on the attribute in which the query terms show up. The order is determined by `searchableAttributes` |
| `rankingRules` | Always | The score is computed by computing the subscore of each ranking rule with a weight that depends on their order |
| `stopWords` | Always | Stop words influence the `words` ranking rule, which is almost always used |
| `synonyms` | Always | Synonyms influence the `words` ranking rule, which is almost always used |
| `typoTolerance` | The `typo` ranking rule is used | Used to compute the maximum number of typos for a query |
# Relevancy
Source: https://www.meilisearch.com/docs/learn/relevancy/relevancy
Relevancy refers to the accuracy of search results. If search results tend to be appropriate for a given query, then they can be considered relevant.
**Relevancy** refers to the accuracy and effectiveness of search results. If search results are almost always appropriate, then they can be considered relevant, and vice versa.
Meilisearch has a number of features for fine-tuning the relevancy of search results. The most important tool among them is **ranking rules**. There are two types of ranking rules: [built-in ranking rules](/learn/relevancy/ranking_rules) and custom ranking rules.
## Behavior
Each index possesses a list of ranking rules stored as an array in the [settings object](/reference/api/settings). This array is fully customizable, meaning you can delete existing rules, add new ones, and reorder them as needed.
Meilisearch uses a [bucket sort](https://en.wikipedia.org/wiki/Bucket_sort) algorithm to rank documents whenever a search query is made. The first ranking rule applies to all documents, while each subsequent rule is only applied to documents considered equal under the previous rule as a tiebreaker.
**The order in which ranking rules are applied matters.** The first rule in the array has the most impact, and the last rule has the least. Our default configuration meets most standard needs, but [you can change it](/reference/api/settings#update-ranking-rules).
Deleting a rule means that Meilisearch will no longer sort results based on that rule. For example, **if you delete the [typo ranking rule](/learn/relevancy/ranking_rules#2-typo), documents with typos will still be considered during search**, but they will no longer be sorted by increasing number of typos.
# Synonyms
Source: https://www.meilisearch.com/docs/learn/relevancy/synonyms
Use Meilisearch synonyms to indicate sets of query terms which should be considered equivalent during search.
If multiple words have an equivalent meaning in your dataset, you can [create a list of synonyms](/reference/api/settings#update-synonyms). This will make your search results more relevant.
Words set as synonyms won't always return the same results. With the default settings, the `movies` dataset should return 547 results for `great` and 66 for `fantastic`. Let's set them as synonyms:
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/movies/settings/synonyms' \
-H 'Content-Type: application/json' \
--data-binary '{
"great": ["fantastic"], "fantastic": ["great"]
}'
```
```javascript JS
client.index('movies').updateSynonyms({
'great': ['fantastic'],
'fantastic': ['great']
})
```
```python Python
client.index('movies').update_synonyms({
'great': ['fantastic'],
'fantastic': ['great']
})
```
```php PHP
$client->index('movies')->updateSynonyms([
'great' => ['fantastic'],
'fantastic' => ['great'],
]);
```
```java Java
HashMap synonyms = new HashMap();
synonyms.put("great", new String[] {"fantastic"});
synonyms.put("fantastic", new String[] {"great"});
client.index("movies").updateSynonymsSettings(synonyms);
```
```ruby Ruby
client.index('movies').update_synonyms({
great: ['fantastic'],
fantastic: ['great']
})
```
```go Go
synonyms := map[string][]string{
"great": []string{"fantastic"},
"fantastic": []string{"great"},
}
client.Index("movies").UpdateSynonyms(&synonyms)
```
```csharp C#
var synonyms = new Dictionary>
{
{ "great", new string[] { "fantastic" } },
{ "fantastic", new string[] { "great" } }
};
await client.Index("movies").UpdateSynonymsAsync(synonyms);
```
```rust Rust
let mut synonyms = std::collections::HashMap::new();
synonyms.insert(String::from("great"), vec![String::from("fantastic")]);
synonyms.insert(String::from("fantastic"), vec![String::from("great")]);
let task: TaskInfo = client
.index("movies")
.set_synonyms(&synonyms)
.await
.unwrap();
```
```swift Swift
let synonyms: [String: [String]] = [
"great": ["fantastic"],
"fantastic": ["great"]
]
client.index("movies").updateSynonyms(synonyms) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').updateSynonyms({
'great': ['fantastic'],
'fantastic': ['great'],
});
```
With the new settings, searching for `great` returns 595 results and `fantastic` returns 423 results. This is due to various factors like [typos](/learn/relevancy/typo_tolerance_settings#minwordsizefortypos) and [splitting the query](/learn/engine/concat#split-queries) to find relevant documents. The search for `great` will allow only one typo (for example, `create`) and take into account all variations of `great` (for instance, `greatest`) along with `fantastic`.
The number of search results may vary depending on changes to the `movies` dataset.
## Normalization
All synonyms are **lowercased** and **de-unicoded** during the indexing process.
### Example
Consider a situation where `Résumé` and `CV` are set as synonyms.
```json
{
"Résumé": [
"CV"
],
"CV": [
"Résumé"
]
}
```
A search for `cv` would return any documents containing `cv` or `CV`, in addition to any that contain `Résumé`, `resumé`, `resume`, etc., unaffected by case or accent marks.
## One-way association
Use this when you want one word to be synonymous with another, but not the other way around.
```
phone => iphone
```
A search for `phone` will return documents containing `iphone` as if they contained the word `phone`.
However, if you search for `iphone`, documents containing `phone` will be ranked lower in the results due to [the typo rule](/learn/relevancy/ranking_rules).
### Example
To create a one-way synonym list, this is the JSON syntax that should be [added to the settings](/reference/api/settings#update-synonyms).
```json
{
"phone": [
"iphone"
]
}
```
## Relevancy
**The exact search query will always take precedence over its synonyms.** The `exactness` ranking rule favors exact words over synonyms when ranking search results.
Taking the following set of search results:
```json
[
{
"id": 0,
"title": "Ghouls 'n Ghosts"
},
{
"id": 1,
"title": "Phoenix Wright: Spirit of Justice"
}
]
```
If you configure `ghost` as a synonym of `spirit`, queries searching for `spirit` will return document `1` before document `0`.
## Mutual association
By associating one or more synonyms with each other, they will be considered the same in both directions.
```
shoe <=> boot <=> slipper <=> sneakers
```
When a search is done with one of these words, all synonyms will be considered as the same word and will appear in the search results.
### Example
To create a mutual association between four words, this is the JSON syntax that should be [added to the settings](/reference/api/settings#update-synonyms).
```json
{
"shoe": [
"boot",
"slipper",
"sneakers"
],
"boot": [
"shoe",
"slipper",
"sneakers"
],
"slipper": [
"shoe",
"boot",
"sneakers"
],
"sneakers": [
"shoe",
"boot",
"slipper"
]
}
```
## Multi-word synonyms
Meilisearch treats multi-word synonyms as [phrases](/reference/api/search#phrase-search).
### Example
Suppose you set `San Francisco` and `SF` as synonyms with a [mutual association](#mutual-association)
```json
{
"san francisco": [
"sf"
],
"sf": [
"san francisco"
]
}
```
If you input `SF` as a search query, Meilisearch will also return results containing the phrase `San Francisco`. However, depending on the ranking rules, they might be considered less [relevant](/learn/relevancy/relevancy) than those containing `SF`. The reverse is also true: if your query is `San Francisco`, documents containing `San Francisco` may rank higher than those containing `SF`.
## Maximum number of synonyms per term
A single term may have up to 50 synonyms. Meilisearch silently ignores any synonyms beyond this limit. For example, if you configure 51 synonyms for `book`, Meilisearch will only return results containing the term itself and the first 50 synonyms.
If any synonyms for a term contain more than one word, the sum of all words across all synonyms for that term cannot exceed 100 words. Meilisearch silently ignores any synonyms beyond this limit. For example, if you configure 40 synonyms for `computer` in your application, taken together these synonyms must contain fewer than 100 words.
# Typo tolerance calculations
Source: https://www.meilisearch.com/docs/learn/relevancy/typo_tolerance_calculations
Typo tolerance helps users find relevant results even when their search queries contain spelling mistakes or typos.
Typo tolerance helps users find relevant results even when their search queries contain spelling mistakes or typos, for example, typing `phnoe` instead of `phone`. You can [configure the typo tolerance feature for each index](/reference/api/settings#update-typo-tolerance-settings).
Meilisearch uses a prefix [Levenshtein algorithm](https://en.wikipedia.org/wiki/Levenshtein_distance) to determine if a word in a document could be a possible match for a query term.
The [number of typos referenced above](/learn/relevancy/typo_tolerance_settings#minwordsizefortypos) is roughly equivalent to Levenshtein distance. The Levenshtein distance between two words *M* and *P* can be thought of as "the minimum cost of transforming *M* into *P*" by performing the following elementary operations on *M*:
* substitution of a character (for example, `kitten` → `sitten`)
* insertion of a character (for example, `siting` → `sitting`)
* deletion of a character (for example, `saturday` → `satuday`)
By default, Meilisearch uses the following rules for matching documents. Note that these rules are **by word** and not for the whole query string.
* If the query word is between `1` and `4` characters, **no typo** is allowed. Only documents that contain words that **start with** or are of the **same length** with this query word are considered valid
* If the query word is between `5` and `8` characters, **one typo** is allowed. Documents that contain words that match with **one typo** are retained for the next steps.
* If the query word contains more than `8` characters, we accept a maximum of **two typos**
This means that `saturday` which is `7` characters long, uses the second rule and matches every document containing **one typo**. For example:
* `saturday` is accepted because it is the same word
* `satuday` is accepted because it contains **one typo**
* `sutuday` is not accepted because it contains **two typos**
* `caturday` is not accepted because it contains **two typos** (as explained [above](/learn/relevancy/typo_tolerance_settings#minwordsizefortypos), a typo on the first letter of a word is treated as two typos)
## Impact of typo tolerance on the `typo` ranking rule
The [`typo` ranking rule](/learn/relevancy/ranking_rules#2-typo) sorts search results by increasing number of typos on matched query words. Documents with 0 typos will rank highest, followed by those with 1 and then 2 typos.
The presence or absence of the `typo` ranking rule has no impact on the typo tolerance setting. However, **[disabling the typo tolerance setting](/learn/relevancy/typo_tolerance_settings#enabled) effectively also disables the `typo` ranking rule.** This is because all returned documents will contain `0` typos.
To summarize:
* Typo tolerance affects how lenient Meilisearch is when matching documents
* The `typo` ranking rule affects how Meilisearch sorts its results
* Disabling typo tolerance also disables `typo`
# Typo tolerance settings
Source: https://www.meilisearch.com/docs/learn/relevancy/typo_tolerance_settings
This article describes each of the typo tolerance settings.
Typo tolerance helps users find relevant results even when their search queries contain spelling mistakes or typos, for example, typing `phnoe` instead of `phone`. You can [configure the typo tolerance feature for each index](/reference/api/settings#update-typo-tolerance-settings).
## `enabled`
Typo tolerance is enabled by default, but you can disable it if needed:
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/indexes/movies/settings/typo-tolerance' \
-H 'Content-Type: application/json' \
--data-binary '{ "enabled": false }'
```
```javascript JS
client.index('movies').updateTypoTolerance({
enabled: false
})
```
```python Python
client.index('movies').update_typo_tolerance({
'enabled': False
})
```
```php PHP
$client->index('movies')->updateTypoTolerance([
'enabled' => false
]);
```
```java Java
TypoTolerance typoTolerance = new TypoTolerance();
typoTolerance.setEnabled(false);
client.index("movies").updateTypoToleranceSettings(typoTolerance);
```
```ruby Ruby
index('books').update_typo_tolerance({ enabled: false })
```
```go Go
client.Index("movies").UpdateTypoTolerance(&meilisearch.TypoTolerance{
Enabled: false,
})
```
```csharp C#
var typoTolerance = new TypoTolerance {
Enabled = false
};
await client.Index("movies").UpdateTypoToleranceAsync(typoTolerance);
```
```rust Rust
let typo_tolerance = TypoToleranceSettings {
enabled: Some(false),
disable_on_attributes: None,
disable_on_words: None,
min_word_size_for_typos: None,
};
let task: TaskInfo = client
.index("movies")
.set_typo_tolerance(&typo_tolerance)
.await
.unwrap();
```
```dart Dart
final toUpdate = TypoTolerance(enabled: false);
await client.index('movies').updateTypoTolerance(toUpdate);
```
With typo tolerance disabled, Meilisearch no longer considers words that are a few characters off from your query terms as matches. For example, a query for `phnoe` will no longer return a document containing the word `phone`.
**In most cases, keeping typo tolerance enabled results in a better search experience.** Massive or multilingual datasets may be exceptions, as typo tolerance can cause false-positive matches in these cases.
## `minWordSizeForTypos`
By default, Meilisearch accepts one typo for query terms containing five or more characters, and up to two typos if the term is at least nine characters long.
If your dataset contains `seven`, searching for `sevem` or `sevan` will match `seven`. But `tow` won't match `two` as it's less than `5` characters.
You can override these default settings using the `minWordSizeForTypos` object. The code sample below sets the minimum word size for one typo to `4` and the minimum word size for two typos to `10`.
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/indexes/movies/settings/typo-tolerance' \
-H 'Content-Type: application/json' \
--data-binary '{
"minWordSizeForTypos": {
"oneTypo": 4,
"twoTypos": 10
}
}'
```
```javascript JS
client.index('movies').updateTypoTolerance({
minWordSizeForTypos: {
oneTypo: 4,
twoTypos: 10
}
})
```
```python Python
client.index('movies').update_typo_tolerance({
'minWordSizeForTypos': {
'oneTypo': 4,
'twoTypos': 10
}
})
```
```php PHP
$client->index('movies')->updateTypoTolerance([
'minWordSizeForTypos' => [
'oneTypo' => 4,
'twoTypos' => 10
]
]);
```
```java Java
TypoTolerance typoTolerance = new TypoTolerance();
HashMap minWordSizeTypos =
new HashMap() {
{
put("oneTypo", 4);
put("twoTypos", 10);
}
};
typoTolerance.setMinWordSizeForTypos(minWordSizeTypos);
client.index("movies").updateTypoToleranceSettings(typoTolerance);
```
```ruby Ruby
index('books').update_typo_tolerance({
min_word_size_for_typos: {
one_typo: 4,
two_typos: 10
}
})
```
```go Go
client.Index("movies").UpdateTypoTolerance(&meilisearch.TypoTolerance{
MinWordSizeForTypos: meilisearch.MinWordSizeForTypos{
OneTypo: 4,
TwoTypos: 10,
},
})
```
```csharp C#
var typoTolerance = new TypoTolerance {
MinWordSizeTypos = new TypoTolerance.TypoSize {
OneTypo = 4,
TwoTypos = 10
}
};
await client.Index("movies").UpdateTypoToleranceAsync(typoTolerance);
```
```rust Rust
let min_word_size_for_typos = MinWordSizeForTypos {
one_typo: Some(4),
two_typos: Some(12)
};
let typo_tolerance = TypoToleranceSettings {
enabled: Some(true),
disable_on_attributes: Some(vec![]),
disable_on_words: Some(vec!["title".to_string()]),
min_word_size_for_typos: Some(min_word_size_for_typos),
};
let task: TaskInfo = client
.index("movies")
.set_typo_tolerance(&typo_tolerance)
.await
.unwrap();
```
```dart Dart
final toUpdate = TypoTolerance(
minWordSizeForTypos: MinWordSizeForTypos(
oneTypo: 4,
twoTypos: 10,
),
);
await client.index('movies').updateTypoTolerance(toUpdate);
```
When updating the `minWordSizeForTypos` object, keep in mind that:
* `oneTypo` must be greater than or equal to 0 and less than or equal to `twoTypos`
* `twoTypos` must be greater than or equal to `oneTypo` and less than or equal to `255`
To put it another way: `0 ≤ oneTypo ≤ twoTypos ≤ 255`.
We recommend keeping the value of `oneTypo` between `2` and `8` and the value of `twoTypos` between `4` and `14`. If either value is too low, you may get a large number of false-positive results. On the other hand, if both values are set too high, many search queries may not benefit from typo tolerance.
**Typo on the first character**\
Meilisearch considers a typo on a query's first character as two typos.
**Concatenation**\
When considering possible candidates for typo tolerance, Meilisearch will concatenate multiple search terms separated by a [space separator](/learn/engine/datatypes#string). This is treated as one typo. For example, a search for `any way` would match documents containing `anyway`.
For more about typo calculations, [see below](/learn/relevancy/typo_tolerance_calculations).
## `disableOnWords`
You can disable typo tolerance for a list of query terms by adding them to `disableOnWords`. `disableOnWords` is case insensitive.
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/indexes/movies/settings/typo-tolerance' \
-H 'Content-Type: application/json' \
--data-binary '{
"disableOnWords": [
"shrek"
]
}'
```
```javascript JS
client.index('movies').updateTypoTolerance({
disableOnWords: ['shrek']
})
```
```python Python
client.index('movies').update_typo_tolerance({
'disableOnWords': ['shrek']
})
```
```php PHP
$client->index('movies')->updateTypoTolerance([
'disableOnWords' => ['shrek']
]);
```
```java Java
TypoTolerance typoTolerance = new TypoTolerance();
typoTolerance.setDisableOnWords(new String[] {"shrek"});
client.index("movies").updateTypoToleranceSettings(typoTolerance);
```
```ruby Ruby
index('books').update_typo_tolerance({ disable_on_words: ['shrek'] })
```
```go Go
client.Index("movies").UpdateTypoTolerance(&meilisearch.TypoTolerance{
DisableOnWords: []string{"shrek"},
})
```
```csharp C#
var typoTolerance = new TypoTolerance {
DisableOnWords = new string[] { "shrek" }
};
await client.Index("movies").UpdateTypoToleranceAsync(typoTolerance);
```
```rust Rust
let min_word_size_for_typos = MinWordSizeForTypos {
one_typo: Some(5),
two_typos: Some(12)
}
let typo_tolerance = TypoToleranceSettings {
enabled: Some(true),
disable_on_attributes: None,
disable_on_words: Some(vec!["shrek".to_string()]),
min_word_size_for_typos: Some(min_word_size_for_typos),
};
let task: TaskInfo = client
.index("movies")
.set_typo_tolerance(&typo_tolerance)
.await
.unwrap();
```
```dart Dart
final toUpdate = TypoTolerance(
disableOnWords: ['shrek'],
);
await client.index('movies').updateTypoTolerance(toUpdate);
```
Meilisearch won't apply typo tolerance on the query term `Shrek` or `shrek` at search time to match documents.
## `disableOnAttributes`
You can disable typo tolerance for a specific [document attribute](/learn/getting_started/documents) by adding it to `disableOnAttributes`. The code sample below disables typo tolerance for `title`:
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/indexes/movies/settings/typo-tolerance' \
-H 'Content-Type: application/json' \
--data-binary '{ "disableOnAttributes": ["title"] }'
```
```javascript JS
client.index('movies').updateTypoTolerance({
disableOnAttributes: ['title']
})
```
```python Python
client.index('movies').update_typo_tolerance({
'disableOnAttributes': ['title']
})
```
```php PHP
$client->index('movies')->updateTypoTolerance([
'disableOnAttributes' => ['title']
]);
```
```java Java
TypoTolerance typoTolerance = new TypoTolerance();
typoTolerance.setDisableOnAttributes(new String[] {"title"});
client.index("movies").updateTypoToleranceSettings(typoTolerance);
```
```ruby Ruby
index('books').update_typo_tolerance({ disable_on_attributes: ['title'] })
```
```go Go
client.Index("movies").UpdateTypoTolerance(&meilisearch.TypoTolerance{
DisableOnAttributes: []string{"title"},
})
```
```csharp C#
var typoTolerance = new TypoTolerance {
DisableOnAttributes = new string[] { "title" }
};
await client.Index("movies").UpdateTypoToleranceAsync(typoTolerance);
```
```rust Rust
let min_word_size_for_typos = MinWordSizeForTypos {
one_typo: Some(5),
two_typos: Some(12)
}
let typo_tolerance = TypoToleranceSettings {
enabled: Some(true),
disable_on_attributes: Some(vec!["title".to_string()]),
disable_on_words: None,
min_word_size_for_typos: None,
};
let task: TaskInfo = client
.index("movies")
.set_typo_tolerance(&typo_tolerance)
.await
.unwrap();
```
```dart Dart
final toUpdate = TypoTolerance(
disableOnAttributes: ['title'],
);
await client.index('movies').updateTypoTolerance(toUpdate);
```
With the above settings, matches in the `title` attribute will not tolerate any typos. For example, a search for `beautiful` (9 characters) will not match the movie "Biutiful" starring Javier Bardem. With the default settings, this would be a match.
## `disableOnNumbers`
You can disable typo tolerance for all numeric values across all indexes and search requests by setting `disableOnNumbers` to `true`:
By default, typo tolerance on numerical values is turned on. This may lead to false positives, such as a search for `2024` matching documents containing `2025` or `2004`.
When `disableOnNumbers` is set to `true`, queries with numbers only return exact matches. Besides reducing the number of false positives, disabling typo tolerance on numbers may also improve indexing performance.
# Comparison to alternatives
Source: https://www.meilisearch.com/docs/learn/resources/comparison_to_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).
## Comparison table
### General overview
| | Meilisearch | Algolia | Typesense | Elasticsearch |
| --------------------- | :--------------------------------------------------------------------------------------------------: | :------------: | :------------------------------------------------------------------------------: | :--------------------------------------------------------------------------: |
| Source code licensing | [MIT](https://choosealicense.com/licenses/mit/) (Fully open-source) | Closed-source | [GPL-3](https://choosealicense.com/licenses/gpl-3.0/) (Fully open-source) | [AGPLv3](https://choosealicense.com/licenses/agpl-3.0/) (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 or vote for it](https://roadmap.meilisearch.com/tabs/1-under-consideration) 😇
| 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](https://roadmap.meilisearch.com/c/60-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 | ✅ | ✅ | ❌ | ✅ |
##### Security
| | Meilisearch | Algolia | Typesense | Elasticsearch |
| ------------------------------------ | :-------------------------------------------------------------------------: | :-----: | :-------: | :-----------------: |
| API Key Management | ✅ | ✅ | ✅ | ✅ |
| Tenant tokens & multi-tenant indexes | ✅ [Multitenancy support](/learn/security/multitenancy_tenant_tokens) | ✅ | ✅ | ✅ Role-based |
##### Search
| | Meilisearch | Algolia | Typesense | Elasticsearch |
| -------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | :-----------: |
| Placeholder search | ✅ | ✅ | ✅ | ✅ |
| Multi-index search | ✅ | ✅ | ✅ | ✅ |
| Federated search | ✅ | ❌ | ❌ | ✅ |
| 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. | 🔶 Does not support `OR` operation across multiple fields | ✅ | ✅ |
| Faceted search | ✅ | ✅ | ✅ Faceted fields must be searchable Faceting can take several seconds when >10 million facet values must be returned | ✅ |
| Distinct attributes
De-duplicate documents by a field value
| ✅ | ✅ | ✅ | ✅ |
| Grouping
Bucket documents by field values
| ❌ | ✅ | ✅ | ✅ |
##### AI-powered search
| | Meilisearch | Algolia | Typesense | Elasticsearch |
| --------------------- | :--------------------------------------------------------------: | :--------------------------: | :--------------------------------: | :-------------------------------------------------------------------------------------------------------------------: |
| Semantic Search | ✅ | 🔶 Under Premium plan | ✅ | ✅ |
| Hybrid Search | ✅ | 🔶 Under Premium plan | ✅ | ✅ |
| Embedding Generation | ✅ OpenAI HuggingFace REST embedders | Undisclosed | OpenAI GCP Vertex AI | ✅ ELSER E5 Cohere OpenAI Azure Google AI Studio Hugging Face |
| Prompt Templates | ✅ | Undisclosed | ❌ | ❌ |
| Vector Store | ✅ | Undisclosed | ✅ | ✅ |
| Langchain Integration | ✅ | ❌ | ✅ | ✅ |
| GPU support | ✅ CUDA | Undisclosed | ✅ CUDA | ❌ |
##### 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 | ❌ | ✅ Cloud Product |
| Monitoring Dashboard | ✅ [Cloud product](https://www.meilisearch.com/docs/learn/analytics/monitoring) | ✅ 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) | ❌ | 🔶 Only for the cloud-hosted solution | ❌ |
| Official cloud-hosted solution | [Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=comparison-table) | ✅ | ✅ | ✅ |
| High availability | Available with [Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=comparison-table) | ✅ | ✅ | ✅ |
| Run-time dependencies | None | N/A | None | None |
| Backward compatibility | ✅ | N/A | ✅ | ✅ |
| Upgrade path | Documents are automatically reindexed on upgrade | N/A | Documents are automatically reindexed on upgrade | Documents are automatically reindexed on upgrade, up to 1 major version |
### 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 | 128GB | 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 | 42K | N/A | 17K | 66K |
| Number of contributors on the main project | 179 | N/A | 38 | 1,900 |
| Public Discord/Slack community size | 2,100 | 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 was inspired by Algolia's product and the algorithms behind it. We indeed studied most of the algorithms and data structures described in their blog posts in order to implement our own. Meilisearch is thus a new search engine based on the work of Algolia and recent research papers.
Meilisearch provides similar features and reaches the same level of relevance just as quickly as its competitor.
If you are a current Algolia user considering a switch to Meilisearch, you may be interested in our [migration guide](/learn/update_and_migration/algolia_migration).
#### Key similarities
Some of the most significant similarities between Algolia and Meilisearch are:
* [Features](/learn/getting_started/what_is_meilisearch#features) 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
Contrary to Algolia, Meilisearch is open-source and can be forked or self-hosted.
Additionally, Meilisearch is written in Rust, a modern systems-level programming language. Rust provides speed, portability, and flexibility, which makes the deployment of our search engine inside virtual machines, containers, or even [Lambda@Edge](https://aws.amazon.com/lambda/edge/) a seamless operation.
#### Pricing
The [pricing model for Algolia](https://www.algolia.com/pricing/) is based on the number of records kept and the number of API operations performed. It can be prohibitively expensive for small and medium-sized businesses.
Meilisearch is an **open-source** search engine available via [Meilisearch Cloud](https://meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=comparison) or self-hosted. Unlike Algolia, [Meilisearch pricing](https://www.meilisearch.com/pricing?utm_campaign=oss\&utm_source=docs\&utm_medium=comparison) is based on the number of documents stored and the number of search operations performed. However, Meilisearch offers a more generous free tier that allows more documents to be stored as well as fairer pricing for search usage. Meilisearch also offers a Pro tier for larger use cases to allow for more predictable pricing.
## 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.
### Source available
#### 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. 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.
While Algolia offers the most advanced and powerful search features, this efficiency comes with an expensive pricing. Moreover, their service is marketed to big companies.
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.
# Contributing to our documentation
Source: https://www.meilisearch.com/docs/learn/resources/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
## Documentation repository and local development
The Meilisearch documentation repository only stores the content of the docs website. Because the code that makes up the website lives in another repository, **it is not possible to run a local copy of the documentation**.
### Handling images and other static assets
When contributing content to the Meilisearch docs, store screenshots, images, GIFs, and videos in the relevant directory under `/assets`.
The build process does not currently support static assets with relative paths. When adding them to a document, make sure the asset URL points to the raw GitHub file address:
```markdown
\!\[Image description\]\(https://raw.githubusercontent.com/meilisearch/documentation/[branch_name]/assets/images/[guide_name]/diagram.png\)
```
## How to contribute?
### Issues
The maintainers of Meilisearch's documentation use [GitHub Issues](https://github.com/meilisearch/documentation/issues/new) to track tasks. Helpful issues include:
* Notify the docs team about content that is inaccurate, outdated, or confusing
* Requests for new features such as versioning or an embedded console
* Requests for new content such as new guides and tutorials
Before opening an issue or PR, please look through our [open issues](https://github.com/meilisearch/documentation/issues) to see if one already exists for your problem. If yes, please leave a comment letting us know that you're waiting for a fix or willing to work on it yourself. If not, please open a new issue describing the problem and informing us whether you want to work on it or not.
We love issues at Meilisearch, because they help us do our jobs better. Nine times out of ten, the most useful contribution is a simple GitHub issue that points out a problem and proposes a solution.
#### Creating your first issue
To open an issue you need a [GitHub account](https://github.com). Create one if necessary, then follow these steps:
1. Log into your account
2. Go to the [Meilisearch Documentation repository](https://github.com/meilisearch/documentation)
3. Click on "Issues"
4. Use the search bar to check if somebody else has reported the same problem. If they have, upvote with a 👍 and **don't create a new issue**!
5. If no one reported the problem you experienced, click on "New issue"
6. Write a short and descriptive title, then add a longer summary explaining the issue. If you're reporting a bug, make sure to include steps to reproduce the error, as well as your OS and browser version
7. Click on "Submit new issue"
8. A member of our team should [get back to you](#how-we-review-contributions) shortly
9. Enjoy the feeling of a job well done! 🎉
### Pull requests
You can also improve the documentation by making a [pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests).
Pull requests ("PRs" for short) are requests to integrate changes into a GitHub repository. The simplest way to create a PR on our documentation is using the "Edit this page" link at the bottom left of every page.
Pull requests are particularly good when you want to:
* **Solve an [existing issue](https://github.com/meilisearch/documentation/issues)**
* Fix a small error, such as a typo or broken link
* Create or improve content about something you know very well—for example, a guide on how to integrate Meilisearch with a tool you have mastered
In most cases, it is a good idea to [create an issue](#creating-your-first-issue) before making a PR. This allows you to coordinate with the documentation maintainers and find the best way of addressing the problem you want to solve.
#### Creating your first PR
To create a PR you need a [GitHub account](https://github.com). Create one if necessary, then 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. If you're not already signed in, do so now. You may be prompted to create a [fork](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 a box named "Propose changes"
5. Fill in the first field to give your PR a short and descriptive title—for example, "Fix typo in search API reference"
6. Use the second field to add a more detailed explanation of the changes you're proposing
7. Click the "Propose changes" button to continue. You should see a page that says "Comparing changes"
8. Make sure the base repository is set to `meilisearch/documentation` and the base branch is set to `main`. You can ignore the remaining fields
9. This screen will also show you a "diff", which allows you to see the changes you made compared to what's currently published on the documentation website
10. Click "Create pull request"
11. **Congrats, you made your first PR!** A documentation maintainer will review your pull request shortly. They may ask for changes, so keep an eye on your GitHub notifications
12. If everything looks good, your work will be merged into the `main` branch and become part of the official documentation site. You are 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)
* Creating written or video content (tutorials, blog posts, etc.)
* Voting on our [public roadmap](https://roadmap.meilisearch.com/tabs/5-ideas)
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/learn/resources/experimental_features_overview
This article covers how to activate 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](/reference/api/experimental_features).
## Activating experimental features at launch
Some experimental features can be [activated at launch](/learn/self_hosted/configure_meilisearch_at_launch), for example with a command-line flag:
```sh
./meilisearch --experimental-enable-metrics
```
Flags and environment variables for experimental features are not included in the [regular configuration options list](/learn/self_hosted/configure_meilisearch_at_launch#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](/reference/api/experimental_features):
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{
"metrics": true
}'
```
```ruby Ruby
client.update_experimental_features(metrics: true)
```
```go Go
client.ExperimentalFeatures().SetMetrics(true).Update()
```
```rust Rust
let client = Client::new("http://localhost:7700", 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.
## Current experimental features
| Name | Description | How to configure |
| --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------- |
| [Limit task batch size](/learn/self_hosted/configure_meilisearch_at_launch) | Limits number of tasks processed in a single batch | CLI flag or environment variable |
| [Log customization](/reference/api/logs) | Customize log output and set up log streams | CLI flag or environment variable, API route |
| [Metrics API](/reference/api/metrics) | Exposes Prometheus-compatible analytics data | CLI flag or environment variable, API route |
| [Reduce indexing memory usage](/learn/self_hosted/configure_meilisearch_at_launch) | Optimizes indexing performance | CLI flag or environment variable |
| [Replication parameters](/learn/self_hosted/configure_meilisearch_at_launch) | Alters task processing for clustering compatibility | CLI flag or environment variable |
| [Search queue size](/learn/self_hosted/configure_meilisearch_at_launch) | Configure maximum number of concurrent search requests | CLI flag or environment variable |
| [`CONTAINS` filter operator](/learn/filtering_and_sorting/filter_expression_reference#contains) | Enables usage of `CONTAINS` with the `filter` search parameter | API route |
| [Edit documents with function](/reference/api/documents#update-documents-with-function) | Use a RHAI function to edit documents directly in the Meilisearch database | API route |
| [`/network` route](/reference/api/network) | Enable `/network` route | API route |
| [Dumpless upgrade](/learn/self_hosted/configure_meilisearch_at_launch#dumpless-upgrade) | Upgrade Meilisearch without generating a dump | API route |
| [Composite embedders](/reference/api/settings#composite-embedders) | Enable composite embedders | API route |
| [Search query embedding cache](/learn/self_hosted/configure_meilisearch_at_launch#search-query-embedding-cache) | Enable a cache for search query embeddings | CLI flag or environment variable |
| [Uncompressed snapshots](/learn/self_hosted/configure_meilisearch_at_launch#uncompressed-snapshots) | Disable snapshot compaction | CLI flag or environment variable |
| [Maximum batch payload size](/learn/self_hosted/configure_meilisearch_at_launch#maximum-batch-payload-size) | Limit batch payload size | CLI flag or environment variable |
| [Multimodal search](/reference/api/settings) | Enable multimodal search | API route |
| [Disable new indexer](/learn/self_hosted/configure_meilisearch_at_launch) | Use previous settings indexer | CLI flag or environment variable |
| [Experimental vector store](/reference/api/settings) | Enables index setting to use experimental vector store | API route |
# FAQ
Source: https://www.meilisearch.com/docs/learn/resources/faq
Frequently asked questions
## 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.
Meilisearch is really **easy to use** and thus accessible to all kinds of developers.
[Take a quick tour](/learn/self_hosted/getting_started_with_self_hosted_meilisearch) to learn the basics of Meilisearch!
We also provide a lot of tools, including [SDKs](/learn/resources/sdks), to help you integrate easily Meilisearch in your project. We're adding new tools every day!
Plus, you can [contact us](https://discord.meilisearch.com) if you need any help.
## How to know if Meilisearch perfectly fits my use cases?
Since Meilisearch is an open-source and easy-to-use tool, you can give it a try using your data. Follow this [guide](/learn/self_hosted/getting_started_with_self_hosted_meilisearch) to get a quick start!
Besides, we published a [comparison between Meilisearch and other search engines](/learn/resources/comparison_to_alternatives) with the goal of providing an overview of Meilisearch alternatives.
## I am trying to add my documents but I keep receiving a `400 - Bad Request` response
The `400 - Bad request` response often means that your data is not in an expected format. You might have extraneous commas, mismatched brackets, missing quotes, etc. Meilisearch API accepts JSON, CSV, and NDJSON formats.
When [adding or replacing documents](/reference/api/documents#add-or-replace-documents), you must enclose them in an array even if there is only one new document.
## I have uploaded my documents, but I get no result when I search in my index
Your document upload probably failed. To understand why, please check the status of the document addition task using the returned [`taskUid`](/reference/api/tasks#get-one-task). If the task failed, the response should contain an `error` object.
Here is an example of a failed task:
```json
{
"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": "internal",
"type": "missing_document_id",
"link": "https://docs.meilisearch.com/errors#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"
}
```
Check your error message for more information.
## Is killing a Meilisearch process safe?
Killing Meilisearch is **safe**, even in the middle of a process (ex: adding a batch of documents). When you restart the server, it will start the task from the beginning.
More information in the [asynchronous operations guide](/learn/async/asynchronous_operations).
## Do you provide a public roadmap for Meilisearch and its integration tools?
Yes, as Meilisearch and its integration tools are open source, we maintain a [public roadmap](https://roadmap.meilisearch.com/) for the general features we plan to do.
For more accurate features and issues, everything is detailed in the issues of all our [GitHub repositories](https://github.com/meilisearch/meilisearch/issues).
## What are the recommended requirements for hosting a Meilisearch instance?
**The short answer:**
The recommended requirements for hosting a Meilisearch instance will depend on many factors, such as the number of documents, the size of those documents, the number of filters/sorts you will need, and more. For a quick estimate to start with, try to use a machine that has at least ten times the disk space of your dataset.
**The long answer:**
Indexing documents is a complex process, making it difficult to accurately estimate the size and memory use of a Meilisearch database. There are a few aspects to keep in mind when optimizing your instance.
### Memory usage
There are two things that can cause your memory usage (RAM) to spike:
1. Adding documents
2. Updating index settings (if index contains documents)
To reduce memory use and indexing time, follow this best practice: **always update index settings before adding your documents**. This avoids unnecessary double-indexing.
### Disk usage
The following factors have a great impact on the size of your database (in no particular order):
* The number of documents
* The size of documents
* The number of searchable fields
* The number of filterable fields
* The size of each update
* The number of different words present in the dataset
Beware heavily multi-lingual datasets and datasets with many unique words, such as IDs or URLs, as they can slow search speed and greatly increase database size. If you do have ID or URL fields, [make them non-searchable](/reference/api/settings#update-searchable-attributes) unless they are useful as search criteria.
### Search speed
Because Meilisearch uses a [memory map](/learn/engine/storage#lmdb), **search speed is based on the ratio between RAM and database size**. In other words:
* A big database + a small amount of RAM => slow search
* A small database + tons of RAM => lightning fast search
Meilisearch also uses disk space as [virtual memory](/learn/engine/storage#memory-usage). This disk space does not correspond to database size; rather, it provides speed and flexibility to the engine by allowing it to go over the limits of physical RAM.
At this time, the number of CPU cores has no direct impact on index or search speed. However, **the more cores you provide to the engine, the more search queries it will be able to process at the same time**.
#### Speeding up Meilisearch
Meilisearch is designed to be fast (≤50ms response time), so speeding it up is rarely necessary. However, if you find that your Meilisearch instance is querying slowly, there are two primary methods to improve search performance:
1. Increase the amount of RAM (or virtual memory)
2. Reduce the size of the database
In general, we recommend the former. However, if you need to reduce the size of your database for any reason, keep in mind that:
* **More relevancy rules => a larger database**
* The proximity [ranking rule](/learn/relevancy/ranking_rules) alone can be responsible for almost 80% of database size
* Adding many attributes to [`filterableAttributes`](/reference/api/settings#filterable-attributes) also consumes a large amount of disk space
* Multi-lingual datasets are costly, so split your dataset—one language per index
* [Stop words](/reference/api/settings#stop-words) are essential to reducing database size
* Not all attributes need to be [searchable](/learn/relevancy/displayed_searchable_attributes#searchable-fields). Avoid indexing unique IDs.
## Why does Meilisearch send data to Segment? Does Meilisearch track its users?
**Meilisearch will never track or identify individual users**. That being said, we do use Segment to collect anonymous data about user trends, feature usage, and bugs.
You can read more about what metrics we collect, why we collect them, and how to disable it on our [telemetry page](/learn/resources/telemetry). Issues of transparency and privacy are very important to us, so if you feel we are lacking in this area please [open an issue](https://github.com/meilisearch/documentation/issues/new/choose) or send an email to our dedicated email address: [privacy@meilisearch.com](mailto:privacy@meilisearch.com).
# Known limitations
Source: https://www.meilisearch.com/docs/learn/resources/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](/learn/engine/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](/learn/self_hosted/configure_meilisearch_at_launch#payload-limit-size) and a [default search limit of 20 hits](/reference/api/search#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](/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](/reference/api/search#query-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](/learn/engine/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](/learn/engine/datatypes#string).
## Maximum number of attributes per document
**Limitation:** Meilisearch can index a maximum of **65,536 attributes per document**. If a document 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`](/learn/self_hosted/configure_meilisearch_at_launch).
## 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, a data type whose size is limited to 511 bytes, to which Meilisearch adds a margin of 44 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](/reference/api/search#filter) may have a maximum filtering depth of 2000.
**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
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
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
# 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
# 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](/reference/api/settings#pagination-object). `maxTotalHits` only applies to the [search route](/reference/api/search) and has no effect on the [get documents with POST](/reference/api/documents#get-documents-with-post) and [get documents with GET](/reference/api/documents#get-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 10GiB.
**Explanation:** Depending on your setup, 10GiB should correspond to 5M to 15M 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 10GiB 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](/reference/api/facet_search), 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/learn/resources/language
Meilisearch is compatible with datasets in any language. Additionally, it features optimized support for languages using whitespace to separate words, Chinese, Hebrew, Japanese, Korean, and Thai.
Meilisearch is multilingual, featuring optimized support for:
* Any language that uses whitespace to separate words
* Chinese
* Hebrew
* Japanese
* Khmer
* Korean
* Swedish
* Thai
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](/learn/indexing/tokenization)
## 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.
### My language does not use whitespace to separate words. Can I still use Meilisearch?
Yes, but search results might be less relevant than in one of the fully optimized languages.
### My language does not use the Roman alphabet. Can I still use Meilisearch?
Yes—our users work with many different alphabets and writing systems, such as Cyrillic, Thai, and Japanese.
### 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/learn/resources/sdks
Meilisearch SDKs are available in many popular programming languages and frameworks. Consult this page for a full list of officially supported libraries.
Our team and community have worked hard to bring Meilisearch to almost all popular web development languages, frameworks, and deployment options.
New integrations are constantly in development. If you'd like to contribute, [see below](/learn/resources/sdks#contributing).
## SDKs
You can use Meilisearch API wrappers in your favorite language. These libraries support all API routes.
* [.NET](https://github.com/meilisearch/meilisearch-dotnet)
* [Dart](https://github.com/meilisearch/meilisearch-dart)
* [Golang](https://github.com/meilisearch/meilisearch-go)
* [Java](https://github.com/meilisearch/meilisearch-java)
* [JavaScript](https://github.com/meilisearch/meilisearch-js)
* [PHP](https://github.com/meilisearch/meilisearch-php)
* [Python](https://github.com/meilisearch/meilisearch-python)
* [Ruby](https://github.com/meilisearch/meilisearch-ruby)
* [Rust](https://github.com/meilisearch/meilisearch-rust)
* [Swift](https://github.com/meilisearch/meilisearch-swift)
## Framework integrations
* Laravel: the official [Laravel-Scout](https://github.com/laravel/scout) package supports Meilisearch.
* [Ruby on Rails](https://github.com/meilisearch/meilisearch-rails)
* [Symfony](https://github.com/meilisearch/meilisearch-symfony)
## Front-end tools
* [Angular](https://github.com/meilisearch/meilisearch-angular)
* [React](https://github.com/meilisearch/meilisearch-react)
* [Vue](https://github.com/meilisearch/meilisearch-vue)
* [Instant Meilisearch](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/instant-meilisearch)
* [Autocomplete client](https://github.com/meilisearch/meilisearch-js-plugins/tree/main/packages/autocomplete-client)
* [docs-searchbar.js](https://github.com/tauri-apps/meilisearch-docsearch)
## DevOps tools
* [meilisearch-kubernetes](https://github.com/meilisearch/meilisearch-kubernetes)
## Platform plugins
* [VuePress plugin](https://github.com/meilisearch/vuepress-plugin-meilisearch)
* [Strapi plugin](https://github.com/meilisearch/strapi-plugin-meilisearch/)
* [Gatsby plugin](https://github.com/meilisearch/gatsby-plugin-meilisearch/)
* [Firebase](https://github.com/meilisearch/firestore-meilisearch)
## AI Assistant tools
* [meilisearch-mcp](https://github.com/meilisearch/meilisearch-mcp): Model Context Protocol server for integrating Meilisearch with AI assistants and tools
* Guide: [Model Context Protocol integration](/guides/ai/mcp)
## Other tools
* [docs-scraper](https://github.com/meilisearch/docs-scraper): a scraper tool to automatically read the content of your documentation and store it into Meilisearch.
## Contributing
If you want to build a new integration for Meilisearch, you are more than welcome to and we would be happy to help you!
We are proud that some of our libraries were developed and are still maintained by external contributors! ♥️
We recommend to follow [these guidelines](https://github.com/meilisearch/integrations-guides) so that it will be easier to integrate your work.
# Telemetry
Source: https://www.meilisearch.com/docs/learn/resources/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
meilisearch --no-analytics
```
```bash
export MEILI_NO_ANALYTICS=true
meilisearch
```
```bash
# 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](/learn/self_hosted/configure_meilisearch_at_launch).
## 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.sort_position` | Position of the `sort` ranking rule | 5 |
| `ranking_rules.exactness_position` | Position of the `exactness` ranking rule if any, otherwise `null` | 6 |
| `ranking_rules.values` | A string representing the ranking rules without the custom asc-desc rules | "words, typo, attribute, sort, 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_replication_parameters` | `true` if `--experimental-replication-parameters` is specified at launch | 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.experimental_dumpless_upgrade` | `true` if instance is launched with the 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/learn/resources/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
Prior to Meilisearch v1, MINOR versions also broke compatibility between releases.
### 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. For example, `meilisearch-go` v0.22 introduced compatibility with Meilisearch v0.30. 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.
It is possible to [access previous versions of the Meilisearch documentation website](/learn/update_and_migration/previous_docs_version), but the process and results are less than ideal. Users are strongly encouraged to always use the latest Meilisearch release.
# Securing your project
Source: https://www.meilisearch.com/docs/learn/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 overview:
### 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
./meilisearch --master-key="MASTER_KEY"
```
UNIX:
```sh
export MEILI_MASTER_KEY="MASTER_KEY"
./meilisearch
```
Windows:
```sh
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 two API keys: `Default Search API Key` and `Default Admin 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 in the same section where you previously located the master key:
### 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
curl -X GET 'MEILISEARCH_URL/keys' \
-H 'Authorization: Bearer MASTER_KEY'
```
```rust Rust
let client = Client::new("http://localhost:7700", Some("MASTER_KEY"));
client
.get_keys()
.await
.unwrap();
```
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 two default API keys:
```json
{
"results": [
{
"name": "Default Search API Key",
"description": "Use it to search from the frontend",
"key": "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33",
"uid": "123-345-456-987-abc",
"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": "123-345-456-987-abc",
"actions": [
"*"
],
"indexes": [
"*"
],
"expiresAt": null,
"createdAt": "2024-01-25T16:19:53.94816Z",
"updatedAt": "2024-01-25T16:19:53.94816Z"
}
],
…
}
```
## 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
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
let client = Client::new("http://localhost:7700", 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
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
let client = Client::new("http://localhost:7700", Some("DEFAULT_SEARCH_API_KEY"));
let index = client.index("medical_records");
index
.search()
.with_query("appointments")
.execute::()
.await
.unwrap();
```
## 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.
# Differences between the master key and API keys
Source: https://www.meilisearch.com/docs/learn/security/differences_master_api_keys
This article explains the main usage differences between the two types of security keys in Meilisearch: master key and API keys.
This article explains the main usage differences between the two types of security keys in Meilisearch: master key and API keys.
## Master key
The master key grants full control over an instance and 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 configured and listed through the `/keys` endpoints.
**Use the master key to create, update, and delete API keys. Do not use it for other operations.**
Consult the [basic security tutorial](/learn/security/basic_security) to learn more about correctly handling your master key.
Exposing the master key can give malicious users complete control over your Meilisearch project. To minimize risks, **only use the master key when managing API keys**.
## API keys
API keys grant access to a specific set of indexes, routes, and endpoints. You can also configure them to expire after a certain date. Use the [`/keys` route](/reference/api/keys) to create, configure, and delete API keys.
**Use API keys for all API operations except API key management.** This includes search, configuring index settings, managing indexes, and adding and updating documents.
In many cases, the default API keys are all you need to safely manage your Meilisearch project. Use the `Default Search API key` for searching, and the `Default Admin API Key` to configure index settings, add documents, and other operations.
# Generate a tenant token without a library
Source: https://www.meilisearch.com/docs/learn/security/generate_tenant_token_scratch
This guide shows you the main steps when creating tenant tokens without using any libraries.
Generating tenant tokens without a library is possible, but not recommended. This guide summarizes the necessary steps.
The full process requires you to create a token header, prepare the data payload with at least one set of search rules, and then sign the token with an API key.
## Prepare token header
The token header must specify a `JWT` type and an encryption algorithm. Supported tenant token encryption algorithms are `HS256`, `HS384`, and `HS512`.
```json
{
"alg": "HS256",
"typ": "JWT"
}
```
## Build token payload
First, create a set of search rules:
```json
{
"INDEX_NAME": {
"filter": "ATTRIBUTE = VALUE"
}
}
```
Next, find your default search API key. Query the [get an API key endpoint](/reference/api/keys#get-one-key) and inspect the `uid` field to obtain your API key's UID:
```sh
curl \
-X GET 'MEILISEARCH_URL/keys/API_KEY' \
-H 'Authorization: Bearer MASTER_KEY'
```
For maximum security, you should also set an expiry date for your tenant tokens. The following Node.js example configures the token to expire 20 minutes after its creation:
```js
parseInt(Date.now() / 1000) + 20 * 60
```
Lastly, assemble all parts of the payload in a single object:
```json
{
"exp": UNIX_TIMESTAMP,
"apiKeyUid": "API_KEY_UID",
"searchRules": {
"INDEX_NAME": {
"filter": "ATTRIBUTE = VALUE"
}
}
}
```
Consult the [token payload reference](/learn/security/tenant_token_reference) for more information on the requirements for each payload field.
## Encode header and payload
You must then encode both the header and the payload into `base64`, concatenate them, and generate the token by signing it using your chosen encryption algorithm.
## Make a search request using a tenant token
After signing the token, you can use it to make search queries in the same way you would use an API key.
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/patient_medical_records/search' \
-H 'Authorization: Bearer TENANT_TOKEN'
```
# Multitenancy and tenant tokens
Source: https://www.meilisearch.com/docs/learn/security/generate_tenant_token_sdk
This guide shows you the main steps when creating tenant tokens using Meilisearch's official SDKs.
There are two steps to use tenant tokens with an official SDK: generating the tenant token, and making a search request using that token.
## Requirements
* a working Meilisearch project
* an application supporting authenticated users
* one of Meilisearch's official SDKs installed
## Generate a tenant token with an official SDK
First, import the SDK. Then create a set of [search rules](/learn/security/tenant_token_reference#search-rules):
```json
{
"patient_medical_records": {
"filter": "user_id = 1"
}
}
```
Search rules must be an object where each key corresponds to an index in your instance. You may configure any number of filters for each index.
Next, find your default search API key. Query the [get an API key endpoint](/reference/api/keys#get-one-key) and inspect the `uid` field to obtain your API key's UID:
```sh
curl \
-X GET 'MEILISEARCH_URL/keys/API_KEY' \
-H 'Authorization: Bearer MASTER_KEY'
```
For maximum security, you should also define an expiry date for tenant tokens.
Finally, send this data to your chosen SDK's tenant token generator:
```javascript JS
import { generateTenantToken } from 'meilisearch/token'
const searchRules = {
patient_medical_records: {
filter: 'user_id = 1'
}
}
const apiKey = 'B5KdX2MY2jV6EXfUs6scSfmC...'
const apiKeyUid = '85c3c2f9-bdd6-41f1-abd8-11fcf80e0f76'
const expiresAt = new Date('2025-12-20') // optional
const token = await generateTenantToken({ apiKey, apiKeyUid, searchRules, expiresAt })
```
```python Python
uid = '85c3c2f9-bdd6-41f1-abd8-11fcf80e0f76';
api_key = 'B5KdX2MY2jV6EXfUs6scSfmC...'
expires_at = datetime(2025, 12, 20)
search_rules = {
'patient_medical_records': {
'filter': 'user_id = 1'
}
}
token = client.generate_tenant_token(api_key_uid=uid, search_rules=search_rules, api_key=api_key, expires_at=expires_at)
```
```php PHP
$apiKeyUid = '85c3c2f9-bdd6-41f1-abd8-11fcf80e0f76';
$searchRules = (object) [
'patient_medical_records' => (object) [
'filter' => 'user_id = 1',
]
];
$options = [
'apiKey' => 'B5KdX2MY2jV6EXfUs6scSfmC...',
'expiresAt' => new DateTime('2025-12-20'),
];
$token = $client->generateTenantToken($apiKeyUid, $searchRules, $options);
```
```java Java
Map filters = new HashMap();
filters.put("filter", "user_id = 1");
Map searchRules = new HashMap();
searchRules.put("patient_medical_records", filters);
Date expiresAt = new SimpleDateFormat("yyyy-MM-dd").parse("2025-12-20");
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
TenantTokenOptions options = new TenantTokenOptions();
options.setApiKey("B5KdX2MY2jV6EXfUs6scSfmC...");
options.setExpiresAt(expiresAt);
String token = client.generateTenantToken("85c3c2f9-bdd6-41f1-abd8-11fcf80e0f76", searchRules, options);
```
```ruby Ruby
uid = '85c3c2f9-bdd6-41f1-abd8-11fcf80e0f76'
api_key = 'B5KdX2MY2jV6EXfUs6scSfmC...'
expires_at = Time.new(2025, 12, 20).utc
search_rules = {
'patient_medical_records' => {
'filter' => 'user_id = 1'
}
}
token = client.generate_tenant_token(uid, search_rules, api_key: api_key, expires_at: expires_at)
```
```go Go
searchRules := map[string]interface{}{
"patient_medical_records": map[string]string{
"filter": "user_id = 1",
},
}
options := &meilisearch.TenantTokenOptions{
APIKey: "B5KdX2MY2jV6EXfUs6scSfmC...",
ExpiresAt: time.Date(2025, time.December, 20, 0, 0, 0, 0, time.UTC),
}
token, err := client.GenerateTenantToken(searchRules, options);
```
```csharp C#
var apiKey = "B5KdX2MY2jV6EXfUs6scSfmC...";
var expiresAt = new DateTime(2025, 12, 20);
var searchRules = new TenantTokenRules(new Dictionary {
{ "patient_medical_records", new Dictionary { { "filter", "user_id = 1" } } }
});
token = client.GenerateTenantToken(
searchRules,
apiKey: apiKey // optional,
expiresAt: expiresAt // optional
);
```
```rust Rust
let api_key = "B5KdX2MY2jV6EXfUs6scSfmC...";
let api_key_uid = "6062abda-a5aa-4414-ac91-ecd7944c0f8d";
let expires_at = time::macros::datetime!(2025 - 12 - 20 00:00:00 UTC);
let search_rules = json!({ "patient_medical_records": { "filter": "user_id = 1" } });
let token = client
.generate_tenant_token(api_key_uid, search_rules, api_key, expires_at)
.unwrap();
```
The SDK will return a valid tenant token.
## Make a search request using a tenant token
After creating a token, you must send it your application's front end. Exactly how to do that depends on your specific setup.
Once the tenant token is available, use it to authenticate search requests as if it were an API key:
```javascript JS
const frontEndClient = new MeiliSearch({ host: 'http://localhost:7700', apiKey: token })
frontEndClient.index('patient_medical_records').search('blood test')
```
```python Python
front_end_client = Client('http://localhost:7700', token)
front_end_client.index('patient_medical_records').search('blood test')
```
```php PHP
$frontEndClient = new Client('http://localhost:7700', $token);
$frontEndClient->index('patient_medical_records')->search('blood test');
```
```java Java
Client frontEndClient = new Client(new Config("http://localhost:7700", token));
frontEndClient.index("patient_medical_records").search("blood test");
```
```ruby Ruby
front_end_client = MeiliSearch::Client.new('http://localhost:7700', token)
front_end_client.index('patient_medical_records').search('blood test')
```
```go Go
client := meilisearch.New("http://localhost:7700", meilisearch.WithAPIKey("masterKey"))
client.Index("patient_medical_records").Search("blood test", &meilisearch.SearchRequest{});
```
```csharp C#
frontEndClient = new MeilisearchClient("http://localhost:7700", token);
var searchResult = await frontEndClient.Index("patient_medical_records").SearchAsync("blood test");
```
```rust Rust
let front_end_client = Client::new("http://localhost:7700", Some(token));
let results: SearchResults = front_end_client
.index("patient_medical_records")
.search()
.with_query("blood test")
.execute()
.await
.unwrap();
```
Applications may use tenant tokens and API keys interchangeably when searching. For example, the same application might use a default search API key for queries on public indexes and a tenant token for logged-in users searching on private data.
# Generate tenant tokens without a Meilisearch SDK
Source: https://www.meilisearch.com/docs/learn/security/generate_tenant_token_third_party
This guide shows you the main steps when creating tenant tokens without using Meilisearch's official SDKs.
This guide shows you the main steps when creating tenant tokens using [`node-jsonwebtoken`](https://www.npmjs.com/package/jsonwebtoken), a third-party library.
## Requirements
* a working Meilisearch project
* a JavaScript application supporting authenticated users
* `jsonwebtoken` v9.0
## Generate a tenant token with `jsonwebtoken`
### Build the tenant token payload
First, create a set of search rules:
```json
{
"INDEX_NAME": {
"filter": "ATTRIBUTE = VALUE"
}
}
```
Next, find your default search API key. Query the [get an API key endpoint](/reference/api/keys#get-one-key) and inspect the `uid` field to obtain your API key's UID:
```sh
curl \
-X GET 'MEILISEARCH_URL/keys/API_KEY' \
-H 'Authorization: Bearer MASTER_KEY'
```
For maximum security, you should also set an expiry date for your tenant tokens. The following example configures the token to expire 20 minutes after its creation:
```js
parseInt(Date.now() / 1000) + 20 * 60
```
### Create tenant token
First, include `jsonwebtoken` in your application. Next, assemble the token payload and pass it to `jsonwebtoken`'s `sign` method:
```js
const jwt = require('jsonwebtoken');
const apiKey = 'API_KEY';
const apiKeyUid = 'API_KEY_UID';
const currentUserID = 'USER_ID';
const expiryDate = parseInt(Date.now() / 1000) + 20 * 60; // 20 minutes
const tokenPayload = {
searchRules: {
'INDEX_NAME': {
'filter': `user_id = ${currentUserID}`
}
},
apiKeyUid: apiKeyUid,
exp: expiryDate
};
const token = jwt.sign(tokenPayload, apiKey, {algorithm: 'HS256'});
```
`sign` requires the payload, a Meilisearch API key, and an encryption algorithm. Meilisearch supports the following encryption algorithms: `HS256`, `HS384`, and `HS512`.
Your tenant token is now ready to use.
Though this example used `jsonwebtoken`, a Node.js package, you may use any JWT-compatible library in whatever language you feel comfortable.
## Make a search request using a tenant token
After signing the token, you can use it to make search queries in the same way you would use an API key.
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/patient_medical_records/search' \
-H 'Authorization: Bearer TENANT_TOKEN'
```
# Multitenancy and tenant tokens
Source: https://www.meilisearch.com/docs/learn/security/multitenancy_tenant_tokens
In this article you'll read what multitenancy is and how tenant tokens help managing complex applications and sensitive data.
In this article you'll read what multitenancy is and how tenant tokens help managing complex applications and sensitive data.
## What is multitenancy?
In software development, multitenancy means that multiple users or tenants share the same computing resources with different levels of access to system-wide data. Proper multitenancy is crucial in cloud computing services such as [DigitalOcean's Droplets](https://www.digitalocean.com/products/droplets) and [Amazon's AWS](https://aws.amazon.com/).
If your Meilisearch application stores sensitive data belonging to multiple users in the same index, you are managing a multi-tenant index. In this context, it is very important to make sure users can only search through their own documents. This can be accomplished with **tenant tokens**.
## What is a tenant token?
Tenant tokens are small packages of encrypted data presenting proof a user can access a certain index. They contain not only security credentials, but also instructions on which documents within that index the user is allowed to see. **Tenant tokens only give access to the search endpoints.** They are meant to be short-lived, so Meilisearch does not store nor keep track of generated tokens.
## What is the difference between tenant tokens and API keys?
API keys give general access to specific actions in an index. An API key with search permissions for a given index can access all information in that index.
Tenant tokens add another layer of control over API keys. They can restrict which information a specific user has access to in an index. If you store private data from multiple customers in a single index, tenant tokens allow you to prevent one user from accessing another's data.
## How to integrate tenant tokens with an application?
Tenant tokens do not require any specific Meilisearch configuration. You can use them exactly the same way as you would use any API key with search permissions.
You must generate tokens in your application. The quickest method to generate tenant tokens is [using an official SDK](/learn/security/generate_tenant_token_sdk). It is also possible to [generate a token with a third-party library](/learn/security/generate_tenant_token_third_party).
## Sample application
Meilisearch developed an in-app search demo using multi-tenancy in a SaaS CRM. It only allows authenticated users to search through contacts, companies, and deals belonging to their organization.
Check out this [sample application](https://saas.meilisearch.com/utm_source=docs) Its code is publicly available in a dedicated [GitHub repository](https://github.com/meilisearch/saas-demo/).
You can also use tenant tokens in role-based access control (RBAC) systems. Consult [How to implement RBAC with Meilisearch](https://blog.meilisearch.com/role-based-access-guide/) on Meilisearch's official blog for more information.
# Protected and unprotected Meilisearch projects
Source: https://www.meilisearch.com/docs/learn/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](/learn/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/learn/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.
# Tenant token payload reference
Source: https://www.meilisearch.com/docs/learn/security/tenant_token_reference
Meilisearch's tenant tokens are JSON web tokens (JWTs). Their payload is made of three elements: search rules, an API key UID, and an optional expiration date.
Meilisearch's tenant tokens are JSON web tokens (JWTs). Their payload is made of three elements: [search rules](#search-rules), an [API key UID](#api-key-uid), and an optional [expiration date](#expiry-date).
## Example payload
```json
{
"exp": 1646756934,
"apiKeyUid": "at5cd97d-5a4b-4226-a868-2d0eb6d197ab",
"searchRules": {
"INDEX_NAME": {
"filter": "attribute = value"
}
}
}
```
## Search rules
The search rules object are a set of instructions defining search parameters Meilisearch will enforced in every query made with a specific tenant token.
### Search rules object
`searchRules` must be a JSON object. Each key must correspond to one or more indexes:
```json
{
"searchRules": {
"*": {},
"INDEX_*": {},
"INDEX_NAME_A": {}
}
}
```
Each search rule object may contain a single `filter` key. This `filter`'s value must be a [filter expression](/learn/filtering_and_sorting/filter_expression_reference):
```json
{
"*": {
"filter": "attribute_A = value_X AND attribute_B = value_Y"
}
}
```
Meilisearch applies the filter to all searches made with that tenant token. A token only has access to the indexes present in the `searchRules` object.
A token may contain rules for any number of indexes. **Specific rulesets take precedence and overwrite `*` rules.**
Because tenant tokens are generated in your application, Meilisearch cannot check if search rule filters are valid. Invalid search rules return throw errors when searching.
Consult the search API reference for [more information on Meilisearch filter syntax](/reference/api/search#filter).
The search rule may also be an empty object. In this case, the tenant token will have access to all documents in an index:
```json
{
"INDEX_NAME": {}
}
```
### Examples
#### Single filter
In this example, the user will only receive `medical_records` documents whose `user_id` equals `1`:
```json
{
"medical_records": {
"filter": "user_id = 1"
}
}
```
#### Multiple filters
In this example, the user will only receive `medical_records` documents whose `user_id` equals `1` and whose `published` field equals `true`:
```json
{
"medical_records": {
"filter": "user_id = 1 AND published = true"
}
}
```
#### Give access to all documents in an index
In this example, the user has access to all documents in `medical_records`:
```json
{
"medical_records": {}
}
```
#### Target multiple indexes with a partial wildcard
In this example, the user will receive documents from any index starting with `medical`. This includes indexes such as `medical_records` and `medical_patents`:
```json
{
"medical*": {
"filter": "user_id = 1"
}
}
```
#### Target all indexes with a wildcard
In this example, the user will receive documents from any index in the whole instance:
```json
{
"*": {
"filter": "user_id = 1"
}
}
```
### Target multiple indexes manually
In this example, the user has access to documents with `user_id = 1` for all indexes, except one. When querying `medical_records`, the user will only have access to published documents:
```json
{
"*": {
"filter": "user_id = 1"
},
"medical_records": {
"filter": "user_id = 1 AND published = true",
}
}
```
## API key UID
Tenant token payloads must include an API key UID to validate requests. The UID is an alphanumeric string identifying an API key:
```json
{
"apiKeyUid": "at5cd97d-5a4b-4226-a868-2d0eb6d197ab"
}
```
Query the [get one API key endpoint](/reference/api/keys) to obtain an API key's UID.
The UID must indicate an API key with access to [the search action](/reference/api/keys#actions). A token has access to the same indexes and routes as the API key used to generate it.
Since a master key is not an API key, **you cannot use a master key to create a tenant token**. Avoid exposing API keys and **always generate tokens on your application's back end**.
If an API key expires, any tenant tokens created with it will become invalid. The same applies if the API key is deleted or regenerated due to a changed master key.
## Expiry date
The expiry date must be a UNIX timestamp or `null`:
```json
{
"exp": 1646756934
}
```
A token's expiration date cannot exceed its parent API key's expiration date.
Setting a token expiry date is optional, but highly recommended. Tokens without an expiry date remain valid indefinitely and may be a security liability.
The only way to revoke a token without an expiry date is to [delete](/reference/api/keys#delete-a-key) its parent API key.
Changing an instance's master key forces Meilisearch to regenerate all API keys and will also render all existing tenant tokens invalid.
# Configure Meilisearch at launch
Source: https://www.meilisearch.com/docs/learn/self_hosted/configure_meilisearch_at_launch
Configure Meilisearch at launch with command-line options, environment variables, or a configuration file.
export const NoticeTag = ({label}) =>
{label}
;
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](/reference/api/settings).
## Command-line options and flags
Pass **command-line options** and their respective values when launching a Meilisearch instance.
```bash
./meilisearch --db-path ./meilifiles --http-addr 'localhost:7700'
```
In the previous example, `./meilisearch` is the command that launches a Meilisearch instance, while `--db-path` and `--http-addr` are options that modify this instance's behavior.
Meilisearch also has a number of **command-line flags.** Unlike command-line options, **flags don't take values**. If a flag is given, it is activated and changes Meilisearch's default behavior.
```bash
./meilisearch --no-analytics
```
The above flag disables analytics for the Meilisearch instance and does not accept a value.
**Both command-line options and command-line flags take precedence over environment variables.** All command-line options and flags are prepended with `--`.
## Environment variables
To configure a Meilisearch instance using environment variables, set the environment variable prior to launching the instance. If you are unsure how to do this, read more about [setting and listing environment variables](https://linuxize.com/post/how-to-set-and-list-environment-variables-in-linux/), or [use a command-line option](#command-line-options-and-flags) instead.
```sh
export MEILI_DB_PATH=./meilifiles
export MEILI_HTTP_ADDR=localhost:7700
./meilisearch
```
```sh
set MEILI_DB_PATH=./meilifiles
set MEILI_HTTP_ADDR=127.0.0.1:7700
./meilisearch
```
In the previous example, `./meilisearch` is the command that launches a Meilisearch instance, while `MEILI_DB_PATH` and `MEILI_HTTP_ADDR` are environment variables that modify this instance's behavior.
Environment variables for command-line flags accept `n`, `no`, `f`, `false`, `off`, and `0` as `false`. An absent environment variable will also be considered as `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 the `.toml` format as an alternative to command-line options and environment variables. Configuration files can be easily shared and versioned, and allow you to define multiple options.
**When used simultaneously, environment variables override the configuration file, and command-line options override environment variables.**
You can download a default configuration file using the following command:
```sh
curl https://raw.githubusercontent.com/meilisearch/meilisearch/latest/config.toml > config.toml
```
By default, Meilisearch will look for a `config.toml` file in the working directory. If it is present, it will be used as the configuration file. You can verify this when you launch Meilisearch:
```
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
Config file path: "./config.toml"
```
If the `Config file path` is anything other than `"none"`, it means that a configuration file was successfully located and used to start Meilisearch.
You can override the default location of the configuration file using the `MEILI_CONFIG_FILE_PATH` environment variable or the `--config-file-path` CLI option:
```sh
./meilisearch --config-file-path="./config.toml"
```
UNIX:
```sh
export MEILI_CONFIG_FILE_PATH="./config.toml"
./meilisearch
```
Windows:
```sh
set MEILI_CONFIG_FILE_PATH="./config.toml"
./meilisearch
```
### Configuration file formatting
You can configure any environment variable or CLI option using a configuration file. 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`.
```toml
import_dump = "./example.dump"
```
Specifying the `config_file_path` option within the configuration file will throw an error. This is the only configuration option that cannot be set within a configuration file.
## Configuring cloud-hosted instances
To configure Meilisearch with command-line options in a cloud-hosted instance, edit its [service file](/guides/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
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 in this page when hosting your project on Meilisearch Cloud.
## 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](/learn/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](/learn/getting_started/search_preview) is disabled
`development`:
* Setting a [master key](/learn/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`](/reference/api/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.](/learn/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](/learn/resources/telemetry#how-to-delete-all-collected-data).
[Read more about our policy on data collection](/learn/resources/telemetry), or take a look at [the comprehensive list of all data points we collect](/learn/resources/telemetry#exhaustive-list-of-all-collected-data).
### Dumpless upgrade
**Environment variable**: `MEILI_EXPERIMENTAL_DUMPLESS_UPGRADE`
**CLI option**: `--experimental-dumpless-upgrade`
**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](/learn/update_and_migration/updating).
#### Create a snapshot before a dumpless upgrade
Take a snapshot of your instance before performing a dumpless upgrade.
Dumpless upgrade 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.
[Learn more about creating dumps](/reference/api/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
🚩 This option does not take any values. Assigning a value will throw an error. 🚩
**Environment variable**: `MEILI_IGNORE_DUMP_IF_DB_EXISTS`
**CLI option**: `--ignore-dump-if-db-exists`
Prevents a Meilisearch instance with an existing database from throwing an error when using `--import-dump`. Instead, the dump will be ignored and Meilisearch will launch using the existing database.
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**: `104857600` (\~100MB)
**Expected value**: an integer
Sets the maximum size of [accepted payloads](/learn/getting_started/documents#dataset-format). Value must be given in bytes or explicitly stating a base unit. For example, the default value can be written as `107374182400`, `'107.7Gb'`, or `'107374 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.
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](/learn/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.
### Uncompressed snapshots
**Environment variable**: `MEILI_EXPERIMENTAL_NO_SNAPSHOT_COMPACTION`
**CLI option**: `--experimental-no-snapshot-compaction`
Disables snapshot compression. This may significantly speed up snapshot creation at the cost of bigger snapshot files.
### 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](/learn/async/asynchronous_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](/learn/async/task_webhook).
The task webhook option requires having access to a command-line interface. If you are using Meilisearch Cloud, use the [`/webhooks` API route](/reference/api/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**: `None`
**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**: `None`
**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.
### Replication parameters
🚩 This option does not take any values. Assigning a value will throw an error. 🚩
**Environment variable**: `MEILI_EXPERIMENTAL_REPLICATION_PARAMETERS`
**CLI option**: `--experimental-replication-parameters`
**Default value**: `None`
Helps running Meilisearch in cluster environments. It does this by modifying task handling in three ways:
* Task auto-deletion is disabled
* Allows you to manually set task uids by adding a custom `TaskId` header to your API requests
* Allows you to dry register tasks by specifying a `DryRun: true` header in your request
### 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.
### 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.
# Getting started with self-hosted Meilisearch
Source: https://www.meilisearch.com/docs/learn/self_hosted/getting_started_with_self_hosted_meilisearch
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](/learn/getting_started/cloud_quick_start).
## Setup and installation
First, you need to download and install Meilisearch. This command installs the latest Meilisearch version in your local machine:
```bash
# 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](/learn/self_hosted/install_meilisearch_locally).
### Running Meilisearch
Next, launch Meilisearch by running the following command in your terminal:
```bash
# 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](/learn/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
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
// With npm:
// npm install meilisearch
// Or with yarn:
// yarn 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: 'http://localhost:7700',
apiKey: 'aSampleMasterKey'
})
client.index('movies').addDocuments(movies)
.then((res) => console.log(res))
```
```python Python
# In the command line:
# pip3 install meilisearch
# In your .py file:
import meilisearch
import json
client = meilisearch.Client('http://localhost:7700', 'aSampleMasterKey')
json_file = open('movies.json', encoding='utf-8')
movies = json.load(json_file)
client.index('movies').add_documents(movies)
```
```php PHP
/**
* 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
// For Maven:
// Add the following code to the `` section of your project:
//
//
// com.meilisearch.sdk
// meilisearch-java
// 0.15.0
// pom
//
// For Gradle
// Add the following line to the `dependencies` section of your `build.gradle`:
//
// implementation 'com.meilisearch.sdk:meilisearch-java:0.15.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("http://localhost:7700", "aSampleMasterKey"));
Index index = client.index("movies");
index.addDocuments(moviesJson);
```
```ruby Ruby
# In the command line:
# bundle add meilisearch
# In your .rb file:
require 'json'
require 'meilisearch'
client = MeiliSearch::Client.new('http://localhost:7700', 'aSampleMasterKey')
movies_json = File.read('movies.json')
movies = JSON.parse(movies_json)
client.index('movies').add_documents(movies)
```
```go Go
// 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("http://localhost:7700", 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#
// 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("http://localhost:7700", "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
// In your .toml file:
[dependencies]
meilisearch-sdk = "0.29.1"
# 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("http://localhost:7700", 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
// 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: "http://localhost:7700", 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
// 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('http://localhost:7700', '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](/learn/getting_started/documents). Each document is an object composed of multiple fields, which are pairs of one attribute and one value:
```json
{
"attribute": "value"
}
```
Documents are grouped into collections, called [indexes](/learn/getting_started/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](/learn/getting_started/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](/learn/getting_started/primary_key#meilisearch-guesses-your-primary-key) it from your dataset.
After adding documents, you should receive a response like this:
```json
{
"taskUid": 0,
"indexUid": "movies",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
Use the returned `taskUid` to [check the status](/reference/api/tasks) of your documents:
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/tasks/0' \
-H 'Authorization: Bearer aSampleMasterKey'
```
```javascript JS
client.tasks.getTask(0)
```
```python Python
client.get_task(0)
```
```php PHP
$client->getTask(0);
```
```java Java
client.getTask(0);
```
```ruby Ruby
client.task(0)
```
```go Go
client.GetTask(0)
```
```csharp C#
TaskInfo task = await client.GetTaskAsync(0);
```
```rust Rust
client
.get_task(0)
.await
.unwrap();
```
```swift Swift
client.getTask(taskUid: 0) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.getTask(0);
```
Most database operations in Meilisearch are [asynchronous](/learn/async/asynchronous_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
{
"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
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer aSampleMasterKey' \
--data-binary '{ "q": "botman" }'
```
```javascript JS
client.index('movies').search('botman').then((res) => console.log(res))
```
```python Python
client.index('movies').search('botman')
```
```php PHP
$client->index('movies')->search('botman');
```
```java Java
client.index("movies").search("botman");
```
```ruby Ruby
client.index('movies').search('botman')
```
```go Go
client.Index("movies").Search("botman", &meilisearch.SearchRequest{})
```
```csharp C#
MeilisearchClient client = new MeilisearchClient("http://localhost:7700", "masterKey");
var index = client.Index("movies");
var movies = await index.SearchAsync("botman");
foreach (var movie in movies.Hits)
{
Console.WriteLine(movie.Title);
}
```
```rust Rust
// 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
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
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
{
"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](/reference/api/search#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](/learn/getting_started/search_preview). You can also [learn how to quickly build a front-end interface](/guides/front_end/front_end_integration) of your own.
For a more advanced approach, consult the [API reference](/reference/api/overview).
# Install Meilisearch locally
Source: https://www.meilisearch.com/docs/learn/self_hosted/install_meilisearch_locally
Use Meilisearch with either Meilisearch Cloud, another cloud service, or install it locally.
You can install Meilisearch locally or deploy it over a cloud service.
## Meilisearch Cloud
[Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=installation-guide) greatly simplifies installing, maintaining, and updating Meilisearch. [Get started with a 14-day free trial](https://cloud.meilisearch.com/register?utm_campaign=oss\&utm_source=docs\&utm_medium=installation-guide).
Take a look at our [Meilisearch Cloud tutorial](/learn/getting_started/cloud_quick_start) 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
# 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
# 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
# Fetch the latest version of Meilisearch image from DockerHub
docker pull getmeili/meilisearch:v1.16
# 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.16
# Use ${pwd} instead of $(pwd) in PowerShell
```
You can learn more about [using Meilisearch with Docker in our dedicated guide](/guides/docker).
Download the **latest stable release** of Meilisearch with **APT**.
Launch Meilisearch to start the server.
```bash
# 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
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
git checkout latest
```
Finally, update the Rust toolchain, compile the project, and execute the binary.
```bash
# 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
# 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
# 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
# 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](/guides/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
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
# 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
# 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
# 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
```
# Supported operating systems
Source: https://www.meilisearch.com/docs/learn/self_hosted/supported_os
Meilisearch officially supports Windows, MacOS, and many Linux distributions.
Meilisearch officially supports Windows, MacOS, and many Linux distributions. Consult the [installation guide](/learn/self_hosted/install_meilisearch_locally) for more instructions.
Meilisearch binaries might still run in unsupported environments.
Use [Meilisearch Cloud](https://www.meilisearch.com/cloud?utm_campaign=oss\&utm_source=docs\&utm_medium=supported-os) to integrate Meilisearch with applications hosted in unsupported operating systems.
## Linux
The Meilisearch binary works on all Linux distributions with `amd64/x86_64` or `aarch64/arm64` architecture using glibc 2.35 and later. You can check your glibc version using:
```
ldd --version
```
## macOS
The Meilisearch binary works with macOS 12 and later with `amd64` or `arm64` architecture.
## Windows
The Meilisearch binary works on Windows Server 2022 and later.
It is likely the Meilisearch binary also works with Windows OS 10 and later. However, due to the differences between Windows OS and Windows Server, Meilisearch does not officially support Windows OS.
## Troubleshooting
If the provided [binaries](https://github.com/meilisearch/meilisearch/releases) do not work on your operating system, try building Meilisearch [from source](/learn/self_hosted/install_meilisearch_locally#local-installation). If compilation fails, Meilisearch is not compatible with your machine.
# Meilisearch Cloud teams
Source: https://www.meilisearch.com/docs/learn/teams/teams
Meilisearch Cloud teams helps collaboration between project stakeholders with different skillsets and responsibilities.
Meilisearch Cloud teams are groups of users who all have access a to specific set of projects. This feature is designed to help collaboration between project stakeholders with different skillsets and responsibilities.
When you open a new account, Meilisearch Cloud automatically creates a default team. A team may have any number of team members.
## Team roles and permissions
There are two types of team members in Meilisearch Cloud teams: owners and regular team members.
Team owners have full control over a project's administrative details. Only team owners may change a project's billing plan or update its billing information. Additionally, only team owners may rename a team, add and remove members from a team, or transfer team ownership.
A team may only have one owner.
## Multiple teams in the same account
If you are responsible for different applications belonging to multiple organizations, it might be useful to create separate teams. There are no limits for the amount of teams a single user may create.
It is not possible to delete a team once you have created it. However, Meilisearch Cloud billing is based on projects and there are no costs associated with creating multiple teams.
# Migrating from Algolia to Meilisearch
Source: https://www.meilisearch.com/docs/learn/update_and_migration/algolia_migration
This guide will take you step-by-step through the creation of a Node.js 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](/learn/resources/comparison_to_alternatives#meilisearch-vs-algolia).
## Overview
This guide will take you step-by-step through the creation of a [Node.js](https://nodejs.org/en/) script to upload Algolia index data to Meilisearch. [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 both Meilisearch and Node.js installed and have access to a command-line terminal. If you're unsure how to install Meilisearch, see our [quick start](/learn/self_hosted/getting_started_with_self_hosted_meilisearch).
This guide was tested with the following package versions:
* [`node.js`](https://nodejs.org/en/): `16.16`
* [`algoliasearch`](https://www.npmjs.com/package/algoliasearch): `4.13`
* [`meilisearch-js`](https://www.npmjs.com/package/meilisearch): `0.27.0`
* [`meilisearch`](https://github.com/meilisearch/meilisearch): `0.28`
## Export your Algolia data
### Initialize project
Start by creating a directory `algolia-meilisearch-migration` and generating a `package.json` file with `npm`:
```bash
mkdir algolia-meilisearch-migration
cd algolia-meilisearch-migration
npm init -y
```
This will set up the environment we need to install dependencies.
Next, create a `script.js` file:
```bash
touch script.js
```
This file will contain our migration script.
### Install dependencies
To get started, you'll need two different packages. The first is `algoliasearch`, the JavaScript client for the Algolia API, and the second is `meilisearch`, the JavaScript client for the Meilisearch API.
```bash
npm install -s algoliasearch@4.13 meilisearch@0.25.1
```
### 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 `script.js`:
```js
const algoliaSearch = require("algoliasearch");
const algoliaClient = algoliaSearch(
"APPLICATION_ID",
"ADMIN_API_KEY"
);
const algoliaIndex = algoliaClient.initIndex("INDEX_NAME");
```
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.
```js
let records = [];
await algoliaIndex.browseObjects({
batch: (hits) => {
records = records.concat(hits);
}
});
```
The `batch` callback method is invoked on each batch of hits and the content is concatenated in the `records` array. 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](/learn/security/basic_security).
```js
const { MeiliSearch } = require("meilisearch");
const meiliClient = new MeiliSearch({
host: "MEILI_HOST",
apiKey: "MEILI_API_KEY",
});
const meiliIndex = meiliClient.index("MEILI_INDEX_NAME");
```
Replace `MEILI_HOST`,`MEILI_API_KEY`, and `MEILI_INDEX_NAME` with your Meilisearch host URL, 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 the Meilisearch JavaScript method [`addDocumentsInBatches`](https://github.com/meilisearch/meilisearch-js#documents-) to upload all your records in batches of 100,000.
```js
const BATCH_SIZE = 100000;
await meiliIndex.addDocumentsInBatches(records, BATCH_SIZE);
```
That's all! When you're ready to run the script, enter the below command:
```bash
node script.js
```
### Finished script
```js
const algoliaSearch = require("algoliasearch");
const { MeiliSearch } = require("meilisearch");
const BATCH_SIZE = 1000;
(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);
})();
```
## 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](/learn/getting_started/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/) is a flexible category that includes both index settings and search parameters. Many API parameters can be used both at indexing time—to set default behavior—or at search time—to override that behavior.
**In Meilisearch,** [index settings](/reference/api/settings) and [search parameters](/reference/api/search#search-parameters) 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**.
| Algolia | Meilisearch |
| :---------------------------------- | :------------------------------------------------------------------------------- |
| `query` | `q` |
| `attributesToRetrieve` | `attributesToRetrieve` |
| `filters` | `filter` |
| `facets` | `facetDistribution` |
| `attributesToHighlight` | `attributesToHighlight` |
| `offset` | `offset` |
| `length` | `limit` |
| `typoTolerance` | `typoTolerance` |
| `snippetEllipsisText` | `cropMarker` |
| `searchableAttributes` | `searchableAttributes` |
| `attributesForFaceting` | `filterableAttributes` |
| `unretrievableAttributes` | No direct equivalent; achieved by removing attributes from `displayedAttributes` |
| `attributesToRetrieve` | `displayedAttributes` |
| `attributeForDistinct` | `distinctAttribute` |
| `ranking` | `rankingRules` |
| `customRanking` | Integrated within `rankingRules` |
| `removeStopWords` | `stopWords` |
| `synonyms` | `synonyms` |
| Sorting(using replicas) | `sortableAttributes` (no replicas required) |
| `removeWordsIfNoResults` | Automatically supported, but not customizable |
| `disableTypoToleranceOnAttributes` | `typoTolerance.disableOnAttributes` |
| `separatorsToIndex` | Not Supported |
| `disablePrefixOnAttributes` | Not Supported |
| `relevancyStrictness` | Not Supported |
| `maxValuesPerFacet` | `maxValuesPerFacet` |
| `sortFacetValuesBy` | `sortFacetValuesBy` |
| `restrictHighlightAndSnippetArrays` | Not Supported |
## 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 to Meilisearch Cloud — Meilisearch Documentation
Source: https://www.meilisearch.com/docs/learn/update_and_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.
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
To follow this guide you need:
* A running Meilisearch instance
* A command-line terminal
* A Meilisearch Cloud account
## Export a dump from your self-hosted installation
To migrate Meilisearch, you must first [export a dump](/learn/data_backup/dumps). A dump is a compressed file containing all your indexes, documents, and settings.
To export a dump, make sure your self-hosted Meilisearch instance is running. Then, open your terminal and run the following command, replacing `MEILISEARCH_URL` with your instance's address:
```sh
curl -X POST 'MEILISEARCH_URL:7700/dumps'
```
Meilisearch will return a summarized task object and begin creating the dump. [Use the returned object's `taskUid` to monitor its progress.](/learn/async/asynchronous_operations)
Once the task has been completed, you can find the dump in your project's dump directory. By default, this is `/dumps`.
Instance configuration options and experimental features that can only be activated at launch are not included in dumps.
Once you have successfully migrated your data to Meilisearch Cloud, use the project overview interface to reactivate available options. Not all instance options are supported in the Cloud.
## Create a Meilisearch Cloud project and import dump
Navigate to Meilisearch Cloud in your browser and log in. If you don't have a Meilisearch Cloud account yet, [create one for free](https://cloud.meilisearch.com/register?utm_campaign=oss\&utm_source=docs\&utm_medium=migration-guide).
You can only import dumps into new Meilisearch Cloud projects. If this is your first time using Meilisearch Cloud, create a new project by clicking on the "Create a project" button. Otherwise, click on the "New project" button:
Fill in your project name, choose a server location, and select your plan. Then, click on the "Import .dump" button and select the dump file you generated in the previous step:
Meilisearch will start creating a new project and importing your data. This might take a few moments depending on the size of your dataset. Monitor the project creation status in the project overview page.
Meilisearch Cloud automatically generates a new master key during project creation. If you are using [security keys](/learn/security/basic_security), update your application so it uses the newly created Meilisearch Cloud API keys.
## Search preview
Once your project is ready, click on it to enter the project overview. From there, click on "Search preview" in the top bar menu. This will bring you to the search preview interface. Run a few test searches to ensure all data was migrated successfully.
Congratulations, you have now migrated to Meilisearch Cloud, the recommended way to use Meilisearch. If you encountered any problems during this process, reach out to our support team on [Discord](https://discord.meilisearch.com).
# Accessing previous docs versions
Source: https://www.meilisearch.com/docs/learn/update_and_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
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
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
yarn install
```
#### Start the local server
After installing dependencies, use Yarn to start the server:
```sh
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
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.
# Update to the latest Meilisearch version
Source: https://www.meilisearch.com/docs/learn/update_and_migration/updating
Learn how to migrate to the latest Meilisearch release.
export const NoticeTag = ({label}) =>
{label}
;
Currently, Meilisearch databases are only compatible with the version of Meilisearch used to create them. The following guide will walk you through using a [dump](/learn/data_backup/dumps) to migrate an existing database from an older version of Meilisearch to the most recent one.
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).
If you are running Meilisearch as a `systemctl` service using v0.22 or above, try our [migration script](https://github.com/meilisearch/meilisearch-migration).
## 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
You may update a self-hosted instance in one of two ways: with or without a dump.
This guide only works for v0.15 and above. If you are using an older Meilisearch release, please [contact support](https://discord.meilisearch.com) for more information.
### Dumpless upgrade
Dumpless upgrades are available when upgrading from Meilisearch >=v1.12 to Meilisearch >=v1.13
#### Step 1: Make a backup
Dumpless upgrades are an experimental feature. Because of that, it may in rare occasions partially fail and result in a corrupted database. To prevent data loss, create a snapshot of your instance:
```sh
curl \
-X POST 'MEILISEARCH_URL/snapshots'
```
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
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
curl -L https://install.meilisearch.com | sh
```
```sh
# 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
Execute the command below to import the dump at launch:
```bash
./meilisearch --experimental-dumpless-upgrade
```
```sh
meilisearch --experimental-dumpless-upgrade
```
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.
#### 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 `--experimental-dumpless-upgrade` flag:
1. Cancel the `databaseUpgrade` 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](/learn/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
#### 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
curl \
-X GET 'http:///version' \
-H 'Authorization: Bearer API_KEY'
```
The response should look something like this:
```json
{
"commitSha": "stringOfLettersAndNumbers",
"commitDate": "YYYY-MM-DDTimestamp",
"pkgVersion": "x.y.z"
}
```
If you get the `missing_authorization_header` error, you might be using **v0.24 or below**. For each command, replace the `Authorization: Bearer` header with the `X-Meili-API-Key: API_KEY` header:
```bash cURL
curl \
-X GET 'http:///version' \
-H 'X-Meili-API-Key: API_KEY'
```
If your [`pkgVersion`](/reference/api/version#version-object) is 0.21 or above, you can jump to [creating the dump](#create-the-dump). If not, proceed to the next step.
##### Set all fields as displayed attributes
If your dump was created in Meilisearch v0.21 or above, [skip this step](#create-the-dump).
When creating dumps using Meilisearch versions below v0.21, all fields must be [displayed](/learn/relevancy/displayed_searchable_attributes#displayed-fields) in order to be saved in the dump.
Start by verifying that all attributes are included in the displayed attributes list:
```bash cURL
# whenever you see {index_uid}, replace it with your index's unique id
curl \
-X GET 'http:///indexes/{index_uid}/settings/displayed-attributes' \
-H 'X-Meili-API-Key: API_KEY'
```
If the response for all indexes is `{'displayedAttributes': '["*"]'}`, you can move on to the [next step](#create-the-dump).
If the response is anything else, save the current list of displayed attributes in a text file and then reset the displayed attributes list to its default value `(["*"])`:
```bash cURL
curl \
-X DELETE 'http:///indexes/{index_uid}/settings/displayed-attributes' \
-H 'X-Meili-API-Key: API_KEY'
```
This command returns an `updateId`. Use the get update endpoint to track the status of the operation:
```sh
# replace {indexUid} with the uid of your index and {updateId} with the updateId returned by the previous request
curl \
-X GET 'http:///indexes/{indexUid}/updates/{updateId}'
-H 'X-Meili-API-Key: API_KEY'
```
Once the status is `processed`, you're good to go. Repeat this process for all indexes, then move on to creating your dump.
##### Create the dump
Before creating your dump, make sure that your [dump directory](/learn/self_hosted/configure_meilisearch_at_launch#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
which meilisearch
```
It should return something like this:
```bash
/absolute/path/to/your/meilisearch/directory
```
```bash
where meilisearch
```
It should return something like this:
```bash
/absolute/path/to/your/meilisearch/directory
```
```bash
(Get-Command meilisearch).Path
```
It should return something like this:
```bash
/absolute/path/to/your/meilisearch/directory
```
Due to an error allowing malformed `_geo` fields in Meilisearch **v0.27, v0.28, and v0.29**, you might not be able to import your dump. Please ensure the `_geo` field follows the [correct format](/learn/filtering_and_sorting/geosearch#preparing-documents-for-location-based-search) before creating your dump.
You can then create a dump of your database:
```bash cURL
curl \
-X POST 'http:///dumps' \
-H 'Authorization: Bearer API_KEY'
# -H 'X-Meili-API-Key: API_KEY' for v0.24 or below
```
The server should return a response that looks like this:
```json
{
"taskUid": 1,
"indexUid": null,
"status": "enqueued",
"type": "dumpCreation",
"enqueuedAt": "2022-06-21T16:10:29.217688Z"
}
```
Use the `taskUid` to [track the status](/reference/api/tasks#get-one-task) of your dump. Keep in mind that the process can take some time to complete.
For v0.27 and below, the response to your request returns a dump `uid`. Use it with the `/dumps/:dump_uid/status` route to track the request status:
```sh
curl \
-X GET 'http:///dumps/:dump_uid/status'
-H 'Authorization: Bearer API_KEY'
# -H 'X-Meili-API-Key: API_KEY' for v0.24 or below
```
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
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](/learn/self_hosted/configure_meilisearch_at_launch#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
curl -L https://install.meilisearch.com | sh
```
```sh
# 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
# 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
# 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](/reference/api/settings#update-displayed-attributes).
### 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 code-base may require some changes. This section contains warnings for some of the most impactful version-specific changes. For full changelogs, see the [releases tab on GitHub](https://github.com/meilisearch/meilisearch/releases).
* If you are updating from **v0.25 or below**, be aware that:
* The `private` and `public` keys have been deprecated and replaced by two default API keys with similar permissions: `Default Admin API Key` and `Default Search API Key`.
* The `updates` API has been replaced with the `tasks` API.
* If you are **updating from v0.27 or below**, existing keys will have their `key` and `uid` fields regenerated.
* If you are **updating from v0.30 or below to v1.0 or above on a cloud platform**, replace `--dumps-dir` with `--dump-dir` in the following files:
* `/etc/systemd/system/meilisearch.service`
* `/var/opt/meilisearch/scripts/first-login/001-setup-prod.sh`
# Batches
Source: https://www.meilisearch.com/docs/reference/api/batches
The /batches route allows you to monitor how Meilisearch is grouping and processing asynchronous operations.
export const RouteHighlighter = ({method, path}) =>
{method}
{path}
;
The `/batches` route gives information about the progress of batches of [asynchronous operations](/learn/async/asynchronous_operations).
## Batch object
```json
{
"uid": 0,
"progress": {
"steps": [
{
"currentStep": "extracting words",
"finished": 2,
"total": 9,
},
{
"currentStep": "document",
"finished": 30546,
"total": 31944,
}
],
"percentage": 32.8471
},
"details": {
"receivedDocuments": 6,
"indexedDocuments": 6
},
"stats": {
"totalNbTasks": 1,
"status": {
"succeeded": 1
},
"types": {
"documentAdditionOrUpdate": 1
},
"indexUids": {
"INDEX_NAME": 1
},
"progressTrace": { … },
"writeChannelCongestion": { … },
"internalDatabaseSizes": { … },
"embedderRequests": {
"total": 12,
"failed": 5,
"lastError": "runtime error: received internal error HTTP 500 from embedding server\n - server replied with `{\"error\":\"Service Unavailable\"}`"
}
},
"duration": "PT0.250518S",
"startedAt": "2024-12-10T15:20:30.18182Z",
"finishedAt": "2024-12-10T15:20:30.432338Z",
"batchStrategy": "batched all enqueued tasks"
}
```
### `uid`
**Type**: Integer
**Description**: Unique sequential identifier of the batch. Starts at `0` and increases by one for every new batch.
### `details`
**Type**: Object
**Description**: Basic information on the types tasks in a batch. Consult the [task object reference](/reference/api/tasks#details) for an exhaustive list of possible values.
### `progress`
**Type**: Object
**Description**: Object containing two fields: `steps` and `percentage`. Once Meilisearch has fully processed a batch, its `progress` is set to `null`.
#### `steps`
Information about the current operations Meilisearch is performing in this batch. A step may consist of multiple substeps.
| Name | Description |
| :---------------- | :------------------------------------------------- |
| **`currentStep`** | A string describing the operation |
| **`total`** | The total number of operations in the step |
| **`finished`** | The number of operations Meilisearch has completed |
If Meilisearch is taking longer than expected to process a batch, monitor the `steps` array. If the `finished` field of the last item in the `steps` array does not update, Meilisearch may be stuck.
#### `percentage`
The percentage of completed operations, calculated from all current steps and substeps. This value is a rough estimate and may not always reflect the current state of the batch due to how different steps are processed more quickly than others.
### `stats`
**Type**: Object
**Description**: Detailed information on the payload of all tasks in a batch.
#### `totalNbTasks`
Number of tasks in the batch.
#### `status`
Object listing the [status of each task](/reference/api/tasks#status) in the batch. Contains five keys whose values correspond to the number of tasks with that status.
#### `types`
List with the `types` of tasks contained in the batch.
#### `indexUids`
List of the number of tasks in the batch separated by the indexes they affect.
#### `progressTrace`
List with full paths for each operation performed in the batch, together with the processing time in human-readable format.
#### `writeChannelCongestion`
Object containing information on write operations computed during indexing. Can be useful when diagnosing performance issues associated with write speeds.
#### `internalDatabaseSizes`
Size of each internal database, including by how much it changed after a batch was processed.
#### `embedderRequests`
Object containing the total number of requests made to the embedder. Also displays the number of failed requests, if any, along with the error message for the most recent failure.
Only present in batches with at least one task querying an embedder. This field continuously updates until Meilisearch finishes processing the batch.
### `duration`
**Type**: String
**Description**: The total elapsed time the batch spent in the `processing` state, in [ISO 8601](https://www.ionos.com/digitalguide/websites/web-development/iso-8601/) format. Set to `null` while the batch is processing tasks
### `startedAt`
**Type**: String
**Description**: The date and time when the batch began `processing`, in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format
### `finishedAt`
**Type**: String
**Description**: The date and time when the tasks finished `processing`, whether `failed`, `succeeded`, or `canceled`, in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format
### `batchStrategy`
**Type**: String
**Description**: A string describing the logic behind the creation of the batch. Can contain useful information when diagnosing indexing performance issues.
## Get batches
List all batches, regardless of index. The batch objects are contained in the `results` array.
Batches are always returned in descending order of `uid`. This means that by default, **the most recently created batch objects appear first**.
Batch results are [paginated](/learn/async/paginating_tasks) and can be [filtered](/learn/async/filtering_tasks) with query parameters.
Some query parameters for `/batches`, such as `uids` and `statuses`, target tasks instead of batches.
For example, `?uids=0` returns a batch containing the task with a `taskUid` equal to `0`, instead of a batch with a `batchUid` equal to `0`.
### Query parameters
| Query Parameter | Default Value | Description |
| ---------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| **`uids`** | `*` (all task uids) | Select batches containing the tasks with the specified `uid`s. Separate multiple task `uids` with a comma (`,`) |
| **`batchUids`** | `*` (all batch uids) | Filter batches by their `uid`. Separate multiple batch `uids` with a comma (`,`) |
| **`indexUids`** | `*` (all indexes) | Select batches containing tasks affecting the specified indexes. Separate multiple `indexUids` with a comma (`,`) |
| **`statuses`** | `*` (all statuses) | Select batches containing tasks with the specified `status`. Separate multiple task `statuses` with a comma (`,`) |
| **`types`** | `*` (all types) | Select batches containing tasks with the specified `type`. Separate multiple task `types` with a comma (`,`) |
| **`limit`** | `20` | Number of batches to return |
| **`from`** | `uid` of the last created batch | `uid` of the first batch returned |
| **`reverse`** | `false` | If `true`, returns results in the reverse order, from oldest to most recent |
| **`beforeEnqueuedAt`** | `*` (all tasks) | Select batches containing tasks with the specified `enqueuedAt` field |
| **`beforeStartedAt`** | `*` (all tasks) | Select batches containing tasks with the specified `startedAt` field |
| **`beforeFinishedAt`** | `*` (all tasks) | Select batches containing tasks with the specified `finishedAt` field |
| **`afterEnqueuedAt`** | `*` (all tasks) | Select batches containing tasks with the specified `enqueuedAt` field |
| **`afterStartedAt`** | `*` (all tasks) | Select batches containing tasks with the specified `startedAt` field |
| **`afterFinishedAt`** | `*` (all tasks) | Select batches containing tasks with the specified `finishedAt` field |
### Response
| Name | Type | Description |
| :------------ | :------ | :----------------------------------------------------------------------------------------------------------------------------- |
| **`results`** | Array | An array of [batch objects](#batch-object) |
| **`total`** | Integer | Total number of batches matching the filter or query |
| **`limit`** | Integer | Number of batches returned |
| **`from`** | Integer | `uid` of the first batch returned |
| **`next`** | Integer | Value passed to `from` to view the next "page" of results. When the value of `next` is `null`, there are no more tasks to view |
### Example
```bash cURL
curl \
-X GET 'http://MEILISEARCH_URL/batches'
```
```javascript JS
client.batches.getBatches();
```
```python Python
client.get_batches()
```
```php PHP
$client->getBatches();
```
```ruby Ruby
client.batches
```
```go Go
client.GetBatches();
```
#### Response: `200 Ok`
```json
{
"results": [
{
"uid": 2,
"progress": null,
"details": {
"stopWords": [
"of",
"the"
]
},
"stats": {
"totalNbTasks": 1,
"status": {
"succeeded": 1
},
"types": {
"settingsUpdate": 1
},
"indexUids": {
"INDEX_NAME": 1
},
"progressTrace": { … },
"writeChannelCongestion": { … },
"internalDatabaseSizes": { … },
"embedderRequests": {
"total": 12,
"failed": 5,
"lastError": "runtime error: received internal error HTTP 500 from embedding server\n - server replied with `{\"error\":\"Service Unavailable\"}`"
}
},
"duration": "PT0.110083S",
"startedAt": "2024-12-10T15:49:04.995321Z",
"finishedAt": "2024-12-10T15:49:05.105404Z",
"batchStrategy": "batched all enqueued tasks"
}
],
"total": 3,
"limit": 1,
"from": 2,
"next": 1
}
```
## Get one batch
Get a single batch.
### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------- |
| **`batch_uid`** \* | String | [`uid`](#uid) of the requested batch |
### Example
```bash cURL
curl \
-X GET 'http://MEILISEARCH_URL/batches/BATCH_UID'
```
```javascript JS
client.batches.getBatch(BATCH_UID);
```
```python Python
client.get_batch(BATCH_UID)
```
```php PHP
$client->getBatch(BATCH_UID);
```
```ruby Ruby
client.batch(BATCH_UID)
```
```go Go
client.GetBatch(BATCH_UID);
```
#### Response: `200 Ok`
```json
{
"uid": 1,
"details": {
"receivedDocuments": 1,
"indexedDocuments": 1
},
"progress": null,
"stats": {
"totalNbTasks": 1,
"status": {
"succeeded": 1
},
"types": {
"documentAdditionOrUpdate": 1
},
"indexUids": {
"INDEX_NAME": 1
},
"progressTrace": { … },
"writeChannelCongestion": { … },
"internalDatabaseSizes": { … },
"embedderRequests": {
"total": 12,
"failed": 5,
"lastError": "runtime error: received internal error HTTP 500 from embedding server\n - server replied with `{\"error\":\"Service Unavailable\"}`"
}
},
"duration": "PT0.364788S",
"startedAt": "2024-12-10T15:48:49.672141Z",
"finishedAt": "2024-12-10T15:48:50.036929Z",
"batchStrategy": "batched all enqueued tasks"
}
```
# Chats
Source: https://www.meilisearch.com/docs/reference/api/chats
Use the chat completion API to create conversational search experiences using LLM technology
export const RouteHighlighter = ({method, path}) =>
{method}
{path}
;
The `/chats` route enables AI-powered conversational search by integrating Large Language Models (LLMs) with your Meilisearch data.
This is an experimental feature. Use the Meilisearch Cloud UI or the experimental features endpoint to activate it:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{
"chatCompletions": true
}'
```
## Authorization
When working with a secure Meilisearch instance, Use an API key with access to both the `search` and `chatCompletions` actions, such as the default chat API key.
Chat queries only search indexes its API key can access. The default chat API key has access to all indexes. To limit chat access to specific indexes, you must either create a new key, or [generate a tenant token](/learn/security/generate_tenant_token_sdk) from the default chat API key.
## Chat workspace object
```json
{
"uid": "WORKSPACE_NAME"
}
```
| Name | Type | Description |
| :-------- | :----- | :--------------------------------------------------- |
| **`uid`** | String | Unique identifier for the chat completions workspace |
## List chat workspaces
List all chat workspaces. Results can be paginated by using the `offset` and `limit` query parameters.
### Query parameters
| Query parameter | Description | Default value |
| :-------------- | :----------------------------- | :------------ |
| **`offset`** | Number of workspaces to skip | `0` |
| **`limit`** | Number of workspaces to return | `20` |
### Response
| Name | Type | Description |
| :------------ | :------ | :----------------------------------------------- |
| **`results`** | Array | An array of [workspaces](#chat-workspace-object) |
| **`offset`** | Integer | Number of workspaces skipped |
| **`limit`** | Integer | Number of workspaces returned |
| **`total`** | Integer | Total number of workspaces |
### Example
```sh
curl \
-X GET 'MEILISEARCH_URL/chats?limit=3'
```
#### Response: `200 Ok`
```json
{
"results": [
{ "uid": "WORKSPACE_1" },
{ "uid": "WORKSPACE_2" },
{ "uid": "WORKSPACE_3" }
],
"offset": 0,
"limit": 20,
"total": 3
}
```
## Get one chat workspace
Get information about a workshop.
### Path parameters
| Name | Type | Description |
| :--------------------- | :----- | :--------------------------- |
| **`workspace_uid`** \* | String | `uid` of the requested index |
### Example
```sh
curl \
-X GET 'MEILISEARCH_URL/chats/WORKSPACE_UID'
```
#### Response: `200 Ok`
```json
{
"uid": "WORKSPACE_UID"
}
```
## Chat workspace settings
### Chat workspace settings object
```json
{
"source": "openAi",
"orgId": null,
"projectId": null,
"apiVersion": null,
"deploymentId": null,
"baseUrl": null,
"apiKey": "sk-abc...",
"prompts": {
"system": "You are a helpful assistant that answers questions based on the provided context."
}
}
```
#### The prompts object
| Name | Type | Description |
| :------------------------ | :----- | :---------------------------------------------------------------------------------------------- |
| **`system`** | String | A prompt added to the start of the conversation to guide the LLM |
| **`searchDescription`** | String | A prompt to explain what the internal search function does |
| **`searchQParam`** | String | A prompt to explain what the `q` parameter of the search function does and how to use it |
| **`searchIndexUidParam`** | String | A prompt to explain what the `indexUid` parameter of the search function does and how to use it |
### Get chat workspace settings
Retrieve the current settings for a chat workspace.
#### Path parameters
| Name | Type | Description |
| :------------------ | :----- | :----------------------- |
| **`workspace_uid`** | String | The workspace identifier |
#### Response: `200 OK`
Returns the settings object. For security reasons, the `apiKey` field is obfuscated.
```json
{
"source": "openAi",
"prompts": {
"system": "You are a helpful assistant."
}
}
```
#### Example
```bash cURL
curl \
-X GET 'http://localhost:7700/chats/WORKSPACE_UID/settings' \
-H 'Authorization: Bearer MEILISEARCH_KEY'
```
### Update chat workspace settings
Configure the LLM provider and settings for a chat workspace.
If the workspace does not exist, querying this endpoint will create it.
#### Path parameters
| Name | Type | Description |
| :------------------ | :----- | :----------------------- |
| **`workspace_uid`** | String | The workspace identifier |
#### Settings parameters
| Name | Type | Description |
| :----------------- | :----- | :---------------------------------------------------------------------------- |
| **`source`** | String | LLM source: `"openAi"`, `"azureOpenAi"`, `"mistral"`, `"gemini"`, or `"vLlm"` |
| **`orgId`** | String | Organization ID for the LLM provider (required for azureOpenAi) |
| **`projectId`** | String | Project ID for the LLM provider |
| **`apiVersion`** | String | API version for the LLM provider (required for azureOpenAi) |
| **`deploymentId`** | String | Deployment ID for the LLM provider (required for azureOpenAi) |
| **`baseUrl`** | String | Base URL for the provider (required for azureOpenAi and vLlm) |
| **`apiKey`** | String | API key for the LLM provider (optional for vLlm) |
| **`prompts`** | Object | Prompts object containing system prompts and other configuration |
#### Request body
```json
{
"source": "openAi",
"apiKey": "OPEN_AI_API_KEY",
"prompts": {
"system": "DEFAULT CHAT INSTRUCTIONS"
}
}
```
All fields are optional. Only provided fields will be updated.
#### Response: `200 OK`
Returns the updated settings object. `apiKey` is write-only and will not be returned in the response.
#### Examples
```bash openAi
curl \
-X PATCH 'http://localhost:7700/chats/customer-support/settings' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"source": "openAi",
"apiKey": "sk-abc...",
"prompts": {
"system": "You are a helpful customer support assistant."
}
}'
```
```bash azureOpenAi
curl \
-X PATCH 'http://localhost:7700/chats/customer-support/settings' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"source": "azureOpenAi",
"orgId": "your-azure-org-id",
"apiVersion": "your-api-version",
"deploymentId": "your-deployment-id",
"apiKey": "your-azure-api-key",
"baseUrl": "https://your-resource.openai.azure.com",
"prompts": {
"system": "You are a helpful customer support assistant."
}
}'
```
```bash mistral
curl \
-X PATCH 'http://localhost:7700/chats/customer-support/settings' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"source": "mistral",
"apiKey": "your-mistral-api-key",
"prompts": {
"system": "You are a helpful customer support assistant."
}
}'
```
```bash gemini
curl \
-X PATCH 'http://localhost:7700/chats/customer-support/settings' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"source": "gemini",
"apiKey": "your-gemini-api-key",
"prompts": {
"system": "You are a helpful customer support assistant."
}
}'
```
```bash vLlm
curl \
-X PATCH 'http://localhost:7700/chats/customer-support/settings' \
-H 'Authorization: Bearer MEILISEARCH_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"source": "vLlm",
"baseUrl": "http://your-vllm-server:8000",
"prompts": {
"system": "You are a helpful customer support assistant."
}
}'
```
### Reset chat workspace settings
Reset a chat workspace's settings to its default values.
#### Path parameters
| Name | Type | Description |
| :------------------ | :----- | :----------------------- |
| **`workspace_uid`** | String | The workspace identifier |
#### Response: `200 OK`
Returns the settings object without the `apiKey` field.
```json
{
"source": "openAi",
"prompts": {
"system": "You are a helpful assistant."
}
}
```
#### Example
```bash cURL
curl \
-X DELETE 'http://localhost:7700/chats/customer-support/settings' \
-H 'Authorization: Bearer MEILISEARCH_KEY'
```
## Chat completions
Create a chat completion using Meilisearch's OpenAI-compatible interface. The endpoint searches relevant indexes and generates responses based on the retrieved content.
### Path parameters
| Name | Type | Description |
| :-------------- | :----- | :---------------------------------------------------- |
| **`workspace`** | String | The chat completion workspace unique identifier (uid) |
### Request body
```json
{
"model": "gpt-3.5-turbo",
"messages": [
{
"role": "user",
"content": "What are the main features of Meilisearch?"
}
],
"stream": true
}
```
| Name | Type | Required | Description |
| :------------- | :------ | :------- | :--------------------------------------------------------------------------- |
| **`model`** | String | Yes | Model to use and will be related to the source LLM in the workspace settings |
| **`messages`** | Array | Yes | Array of message objects with `role` and `content` |
| **`stream`** | Boolean | No | Enable streaming responses (default: `true`) |
Currently, only streaming responses (`stream: true`) are supported.
### Message object
| Name | Type | Description |
| :------------ | :----- | :--------------------------------------------------- |
| **`role`** | String | Message role: `"system"`, `"user"`, or `"assistant"` |
| **`content`** | String | Message content |
### Response
The response follows the OpenAI chat completions format. For streaming responses, the endpoint returns Server-Sent Events (SSE).
#### Streaming response example
```
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"gpt-3.5-turbo","choices":[{"index":0,"delta":{"content":"Meilisearch"},"finish_reason":null}]}
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"gpt-3.5-turbo","choices":[{"index":0,"delta":{"content":" is"},"finish_reason":null}]}
data: {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1677652288,"model":"gpt-3.5-turbo","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
```
### Example
```bash cURL
curl -N \
-X POST 'http://localhost:7700/chats/customer-support/chat/completions' \
-H 'Authorization: Bearer DEFAULT_CHAT_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"model": "gpt-3.5-turbo",
"messages": [
{
"role": "user",
"content": "What is Meilisearch?"
}
],
"stream": true
}'
```
```javascript Javascript OpenAI SDK
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'http://localhost:7700/chats/customer-support',
apiKey: 'DEFAULT_CHAT_KEY',
});
const stream = await client.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: 'What is Meilisearch?' }],
stream: true,
});
for await (const chunk of stream) {
console.log(chunk.choices[0]?.delta?.content || '');
}
```
```python Python OpenAI SDK
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:7700/chats/customer-support",
api_key="DEFAULT_CHAT_KEY"
)
stream = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "What is Meilisearch?"}],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="")
```
# Documents
Source: https://www.meilisearch.com/docs/reference/api/documents
The /documents route allows you to create, manage, and delete documents.
export const NoticeTag = ({label}) =>
{label}
;
export const RouteHighlighter = ({method, path}) =>
{method}
{path}
;
The `/documents` route allows you to create, manage, and delete documents.
[Learn more about documents.](/learn/getting_started/documents)
## Get documents with POST
Get a set of documents.
Use `offset` and `limit` to browse through documents.
`filter` will not work without first explicitly adding attributes to the [`filterableAttributes` list](/reference/api/settings#update-filterable-attributes). [Learn more about filters in our dedicated guide.](/learn/filtering_and_sorting/filter_search_results)
### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
### Body
| Name | Type | Default Value | Description |
| :-------------------- | :-------------------------------------- | :---------------------------------------------------------------------- | :-------------------------------------------------------------------- |
| **`offset`** | Integer | `0` | Number of documents to skip |
| **`limit`** | Integer | `20` | Number of documents to return |
| **`fields`** | Array of strings/`null` | `*` | Document attributes to show (case-sensitive, comma-separated) |
| **`filter`** | String/Array of array of strings/`null` | N/A | Refine results based on attributes in the `filterableAttributes` list |
| **`retrieveVectors`** | Boolean | `false` | Return document vector data with search result |
| **`sort`** | `null` | A list of attributes written as an array or as a comma-separated string | |
| **`ids`** | Array of primary keys | `null` | Return documents based on their primary keys |
Sending an empty payload (`--data-binary '{}'`) will return all documents in the index.
### Response
| Name | Type | Description |
| :------------ | :------ | :------------------------------------- |
| **`results`** | Array | An array of documents |
| **`offset`** | Integer | Number of documents skipped |
| **`limit`** | Integer | Number of documents returned |
| **`total`** | Integer | Total number of documents in the index |
#### Returned document order
`/indexes/{index_uid}/documents/fetch` and `/indexes/{index_uid}/documents` responses do not return documents following the order of their primary keys.
### Example
```bash cURL
curl \
-X POST MEILISEARCH_URL/indexes/books/documents/fetch \
-H 'Content-Type: application/json' \
--data-binary '{
"filter": "(rating > 3 AND (genres = Adventure OR genres = Fiction)) AND language = English",
"fields": ["title", "genres", "rating", "language"],
"limit": 3
}'
```
```javascript JS
client.index('books').getDocuments({
filter: '(rating > 3 AND (genres = Adventure OR genres = Fiction)) AND language = English',
fields: ['title', 'genres', 'rating', 'language'],
limit: 3
})
```
```python Python
client.index('books').get_documents({
'limit':3,
'fields': ['title', 'genres', 'rating', 'language'],
'filter': '(rating > 3 AND (genres=Adventure OR genres=Fiction)) AND language=English'
})
```
```php PHP
$client->index('books')->getDocuments(
(new DocumentsQuery())
->setFilter('(rating > 3 AND (genres = Adventure OR genres = Fiction)) AND language = English')
->setLimit(3)
->setFields(['title', 'genres', 'rating', 'language'])
);
```
```java Java
DocumentsQuery query = new DocumentsQuery()
.setFilter(new String[] {"(rating > 3 AND (genres = Adventure OR genres = Fiction)) AND language = English"})
.setFields(new String[] {"title", "genres", "rating", "language"})
.setLimit(3);
client.index("books").getDocuments(query, TargetClassName.class);
```
```ruby Ruby
client.index('books').get_documents(
filter: '(rating > 3 AND (genres = Adventure OR genres = Fiction)) AND language = English',
limit: 3,
fields: ['title', 'genres', 'rating', 'language']
)
```
```go Go
var result meilisearch.DocumentsResult
client.Index("books").GetDocuments(&meilisearch.DocumentsQuery{
Fields: []string{"title", "genres", "rating", "language"},
Filter: "(rating > 3 AND (genres = Adventure OR genres = Fiction)) AND language = English",
}, &result)
```
```csharp C#
await client.Index("movies").GetDocumentsAsync(new DocumentsQuery() {
Limit = 3,
Fields = new List { "title", "genres", "rating", "language"},
Filter = "(rating > 3 AND (genres=Adventure OR genres=Fiction)) AND language=English"
});
```
```rust Rust
let index = client.index("books");
let documents: DocumentsResults = DocumentsQuery::new(&index)
.with_filter("(rating > 3 AND (genres = Adventure OR genres = Fiction)) AND language = English")
.with_fields(["title", "genres", "rating", "language"])
.with_limit(2)
.execute::()
.await
.unwrap();
```
```dart Dart
await client.index('movies').getDocuments(
params: DocumentsQuery(
filterExpression: Meili.and([
'language'.toMeiliAttribute().eq('English'.toMeiliValue()),
Meili.and([
'rating'.toMeiliAttribute().gt(3.toMeiliValue()),
Meili.or([
'genres'.toMeiliAttribute().eq('Adventure'.toMeiliValue()),
'genres'.toMeiliAttribute().eq('Fiction'.toMeiliValue()),
]),
]),
]),
fields: ['title', 'genres', 'rating', 'language'],
limit: 3,
),
);
```
#### Response: `200 Ok`
```json
{
"results": [
{
"title": "The Travels of Ibn Battuta",
"genres": [
"Travel",
"Adventure"
],
"language": "English",
"rating": 4.5
},
{
"title": "Pride and Prejudice",
"genres": [
"Classics",
"Fiction",
"Romance",
"Literature"
],
"language": "English",
"rating": 4
},
…
],
"offset": 0,
"limit": 3,
"total": 5
}
```
## Get documents with GET
This endpoint will be deprecated in the near future. Consider using POST `/indexes/{index_uid}/documents/fetch` instead.
Get a set of documents.
Using the query parameters `offset` and `limit`, you can browse through all your documents.`filter` must be a string. To create [filter expressions](/learn/filtering_and_sorting/filter_expression_reference) use `AND` or `OR`.
`filter` will not work without first explicitly adding attributes to the [`filterableAttributes` list](/reference/api/settings#update-filterable-attributes). [Learn more about filters in our dedicated guide.](/learn/filtering_and_sorting/filter_search_results)
### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
### Query parameters
| Query Parameter | Default Value | Description |
| :-------------------- | :------------ | :-------------------------------------------------------------------- |
| **`offset`** | `0` | Number of documents to skip |
| **`limit`** | `20` | Number of documents to return |
| **`fields`** | `*` | Document attributes to show (case-sensitive, comma-separated) |
| **`filter`** | N/A | Refine results based on attributes in the `filterableAttributes` list |
| **`retrieveVectors`** | `false` | Return document vector data with search result |
| **`sort`** | `null` | A list of comma-separated attributes |
| **`ids`** | `null` | Return documents based on their primary keys |
### Response
| Name | Type | Description |
| :------------ | :------ | :------------------------------------- |
| **`results`** | Array | An array of documents |
| **`offset`** | Integer | Number of documents skipped |
| **`limit`** | Integer | Number of documents returned |
| **`total`** | Integer | Total number of documents in the index |
#### Returned document order
`/indexes/{index_uid}/documents/fetch` and `/indexes/{index_uid}/documents` responses do not return documents following the order of their primary keys.
### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/movies/documents?limit=2&filter=genres=action'
```
```javascript JS
client.index('movies').getDocuments({
limit: 2,
filter: 'genres = action'
})
```
```python Python
client.index('movies').get_documents({'limit':2, 'filter': 'genres=action'})
```
```php PHP
$client->index('movies')->getDocuments((new DocumentsQuery())->setFilter('genres = action')->setLimit(2));
```
```java Java
DocumentsQuery query = new DocumentsQuery().setLimit(2).setFilter(new String[] {"genres = action"});
client.index("movies").getDocuments(query, TargetClassName.class);
```
```ruby Ruby
client.index('movies').get_documents(limit: 2, filter: 'genres = action')
```
```go Go
var result meilisearch.DocumentsResult
client.Index("movies").GetDocuments(&meilisearch.DocumentsQuery{
Limit: 2,
Filter: "genres = action",
}, &result)
```
```csharp C#
await client.Index("movies").GetDocumentsAsync(new DocumentsQuery() { Limit = 2, Filter = "genres = action" });
```
```rust Rust
let index = client.index("movies");
let documents: DocumentsResults = DocumentsQuery::new(&index)
.with_filter("genres = action")
.with_limit(2)
.execute::()
.await
.unwrap();
```
```swift Swift
client.index("movies").getDocuments(params: DocumentsQuery(limit: 2)) { (result: Result, Swift.Error>) in
switch result {
case .success(let movies):
print(movies)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').getDocuments(
params: DocumentsQuery(
limit: 2,
filter: Meili.attr('genres').eq('action'.toMeiliValue()),
),
);
```
#### Response: `200 Ok`
```json
{
"results": [
{
"id": 364,
"title": "Batman Returns",
"overview": "While Batman deals with a deformed man calling himself the Penguin, an employee of a corrupt businessman transforms into the Catwoman.",
"genres": [
"Action",
"Fantasy"
],
"poster": "https://image.tmdb.org/t/p/w500/jKBjeXM7iBBV9UkUcOXx3m7FSHY.jpg",
"release_date": 708912000
},
{
"id": 13851,
"title": " Batman: Gotham Knight",
"overview": "A collection of key events mark Bruce Wayne's life as he journeys from beginner to Dark Knight.",
"genres": [
"Animation",
"Action",
"Adventure"
],
"poster": "https://image.tmdb.org/t/p/w500/f3xUrqo7yEiU0guk2Ua3Znqiw6S.jpg",
"release_date": 1215475200
}
],
"offset": 0,
"limit": 2,
"total": 5403
}
```
## Get one document
Get one document using its unique id.
### Path parameters
| Name | Type | Description |
| :------------------- | :------------- | :-------------------------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
| **`document_id`** \* | String/Integer | [Document id](/learn/getting_started/primary_key#document-id) of the requested document |
### Query parameters
| Query Parameter | Default Value | Description |
| :-------------------- | :------------ | :------------------------------------------------------------ |
| **`fields`** | `*` | Document attributes to show (case-sensitive, comma-separated) |
| **`retrieveVectors`** | `false` | Return document vector data with search result |
### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/movies/documents/25684?fields=id,title,poster,release_date'
```
```javascript JS
client
.index('movies')
.getDocument(25684, { fields: ['id', 'title', 'poster', 'release_date'] })
```
```python Python
client.index('movies').get_document(25684, {
'fields': ['id', 'title', 'poster', 'release_date']
})
```
```php PHP
$client->index('movies')->getDocument(25684, ['id', 'title', 'poster', 'release_date']);
```
```java Java
DocumentQuery query = new DocumentQuery().setFields(new String[] {"id", "title", "poster", "release_date"});
client.index("movies").getDocument("25684", query);
```
```ruby Ruby
client.index('movies').document(25684, fields: ['id', 'title', 'poster', 'release_date'])
```
```go Go
var a interface{}
client.Index("movies").GetDocument("25684",&meilisearch.DocumentQuery{
Fields: []string{"id", "title", "poster", "release_date"},
}, &a)
```
```csharp C#
await client.Index("movies").GetDocumentAsync(25684, new List { "id", "title", "poster", "release_date" });
```
```rust Rust
let index = client
.index("movies");
let document = DocumentQuery::new(&index)
.with_fields(["id", "title", "poster", "release_date"])
.execute::("25684")
.await
.unwrap();
```
```swift Swift
client.index("movies").getDocument(25684) { (result: Result) in
switch result {
case .success(let movie):
print(movie)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').getDocument(25684,
fields: ['id', 'title', 'poster', 'release_date']);
```
#### Response: `200 Ok`
```json
{
"id": 25684,
"title": "American Ninja 5",
"poster": "https://image.tmdb.org/t/p/w1280/iuAQVI4mvjI83wnirpD8GVNRVuY.jpg",
"release_date": "1993-01-01"
}
```
## Add or replace documents
Add an array of documents or replace them if they already exist. If the provided index does not exist, it will be created.
If you send an already existing document (same [document id](/learn/getting_started/primary_key#document-id)) the **whole existing document** will be overwritten by the new document. Fields that are no longer present in the new document are removed. For a partial update of the document see the [add or update documents](/reference/api/documents#add-or-update-documents) endpoint.
This endpoint accepts the following content types:
* `application/json`
* `application/x-ndjson`
* `text/csv`
### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
### Query parameters
| Query Parameter | Default Value | Description |
| :----------------- | :------------ | :-------------------------------------------------------------------------------------------------------------------------------------- |
| **`primaryKey`** | `null` | [Primary key](/learn/getting_started/primary_key#primary-field) of the index |
| **`csvDelimiter`** | `","` | Configure the character separating CSV fields. Must be a string containing [one ASCII character](https://www.rfc-editor.org/rfc/rfc20). |
Configuring `csvDelimiter` and sending data with a content type other than CSV will cause Meilisearch to throw an error.
If you want to [set the primary key of your index on document addition](/learn/getting_started/primary_key#setting-the-primary-key-on-document-addition), it can only be done **the first time you add documents** to the index. After this, the `primaryKey` parameter will be ignored if given.
### Body
An array of documents. Each document is represented as a JSON object.
```json
[
{
"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"
}
]
```
#### `_vectors`
`_vectors` is a special document attribute containing an object with vector embeddings for AI-powered search.
Each key of the `_vectors` object must be the name of a configured embedder and correspond to a nested object with two fields, `embeddings` and `regenerate`:
```json
[
{
"id": 452,
"title": "Female Trouble",
"overview": "Delinquent high school student Dawn Davenport runs away from home and embarks upon a life of crime.",
"_vectors": {
"default": {
"embeddings": [0.1, 0.2, 0.3],
"regenerate": false
},
"ollama": {
"embeddings": [0.4, 0.12, 0.6],
"regenerate": true
}
}
}
]
```
`embeddings` is an optional field. It must be an array of numbers representing a single embedding for that document. It may also be an array of arrays of numbers representing multiple embeddings for that document. `embeddings` defaults to `null`.
`regenerate` is a mandatory field. It must be a Boolean value. If `regenerate` is `true`, Meilisearch automatically generates embeddings for that document immediately and every time the document is updated. If `regenerate` is `false`, Meilisearch keeps the last value of the embeddings on document updates.
You may also use an array shorthand to add embeddings to a document:
```json
"_vectors": {
"default": [0.003, 0.1, 0.75]
}
```
Vector embeddings added with the shorthand are not replaced when Meilisearch generates new embeddings. The above example is equivalent to:
```json
"default": {
"embeddings": [0.003, 0.1, 0.75],
"regenerate": false
}
```
If the key for an embedder inside `_vectors` is empty or `null`, Meilisearch treats the document as not having any embeddings for that embedder. This document is then returned last during AI-powered searches.
### Example
```bash cURL
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
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'
}])
```
```python Python
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'
}])
```
```php PHP
$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
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
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
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",
},
}
client.Index("movies").AddDocuments(documents, nil)
```
```csharp C#
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
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
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
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'
}
]);
```
#### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Add or update documents
Add a list of documents or update them if they already exist. If the provided index does not exist, it will be created.
If you send an already existing document (same [document id](/learn/getting_started/primary_key#document-id)) the old document will be only partially updated according to the fields of the new document. Thus, any fields not present in the new document are kept and remain unchanged.
To completely overwrite a document, check out the [add or replace documents route](/reference/api/documents#add-or-replace-documents).
If you want to set the [**primary key** of your index](/learn/getting_started/primary_key#setting-the-primary-key-on-document-addition) through this route, you may only do so **the first time you add documents** to the index. If you try to set the primary key after having added documents to the index, the task will return an error.
This endpoint accepts the following content types:
* `application/json`
* `application/x-ndjson`
* `text/csv`
### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
### Query parameters
| Query Parameter | Default Value | Description |
| :----------------- | :------------ | :-------------------------------------------------------------------------------------------------------------------------------------- |
| **`primaryKey`** | `null` | [Primary key](/learn/getting_started/primary_key#primary-field) of the documents |
| **`csvDelimiter`** | `","` | Configure the character separating CSV fields. Must be a string containing [one ASCII character](https://www.rfc-editor.org/rfc/rfc20). |
Configuring `csvDelimiter` and sending data with a content type other than CSV will cause Meilisearch to throw an error.
### Body
An array of documents. Each document is represented as a JSON object.
```json
[
{
"id": 287947,
"title": "Shazam ⚡️"
}
]
```
### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/movies/documents' \
-H 'Content-Type: application/json' \
--data-binary '[
{
"id": 287947,
"title": "Shazam ⚡️",
"genres": "comedy"
}
]'
```
```javascript JS
client.index('movies').updateDocuments([{
id: 287947,
title: 'Shazam ⚡️',
genres: 'comedy'
}])
```
```python Python
client.index('movies').update_documents([{
'id': 287947,
'title': 'Shazam ⚡️',
'genres': 'comedy'
}])
```
```php PHP
$client->index('movies')->updateDocuments([
[
'id' => 287947,
'title' => 'Shazam ⚡️',
'genres' => 'comedy'
]
]);
```
```java Java
client.index("movies").updateDocuments("[{
+ "\"id\": 287947,"
+ "\"title\": \"Shazam ⚡️\","
+ "\"genres\": \"comedy\""
+ "}]"
);
```
```ruby Ruby
client.index('movies').update_documents([
{
id: 287947,
title: 'Shazam ⚡️',
genres: 'comedy'
}
])
```
```go Go
documents := []map[string]interface{}{
{
"id": 287947,
"title": "Shazam ⚡️",
"genres": "comedy",
},
}
client.Index("movies").UpdateDocuments(documents, nil)
```
```csharp C#
var movie = new[]
{
new Movie { Id = "287947", Title = "Shazam ⚡️", Genres = "comedy" }
};
await index.UpdateDocumentsAsync(movie);
```
```rust Rust
// Define the type of our documents
#[derive(Serialize, Deserialize)]
struct IncompleteMovie {
id: usize,
title: String
}
let task: TaskInfo = client
.index("movies")
.add_or_update(&[
IncompleteMovie {
id: 287947,
title: "Shazam ⚡️".to_string()
}
], None)
.await
.unwrap();
```
```swift Swift
let documentJsonString = """
[
{
"reference_number": 287947,
"title": "Shazam ⚡️",
"genres": "comedy"
}
]
"""
let documents: Data = documentJsonString.data(using: .utf8)!
client.index("movies").updateDocuments(documents: documents) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').updateDocuments([
{
'id': 287947,
'title': 'Shazam ⚡️',
'genres': 'comedy',
}
]);
```
This document is an update of the document found in [add or replace document](/reference/api/documents#add-or-replace-documents).
The documents are matched because they have the same [primary key](/learn/getting_started/documents#primary-field) value `id: 287947`. This route will update the `title` field as it changed from `Shazam` to `Shazam ⚡️` and add the new `genres` field to that document. The rest of the document will remain unchanged.
#### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Update documents with function
Use a [RHAI function](https://rhai.rs/book/engine/hello-world.html) to edit one or more documents directly in Meilisearch.
This is an experimental feature. Use the experimental features endpoint to activate it:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{
"editDocumentsByFunction": true
}'
```
### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
### Query parameters
| Query Parameter | Default Value | Description |
| :-------------- | :------------ | :----------------------------------------------------------------------------- |
| **`function`** | `null` | A string containing a RHAI function |
| **`filter`** | `null` | A string containing a filter expression |
| **`context`** | `null` | An object with data Meilisearch should make available for the editing function |
#### `function`
`function` must be a string with a RHAI function that Meilisearch will apply to all selected documents. By default this function has access to a single variable, `doc`, representing the document you are currently editing. This is a required field.
#### `filter`
`filter` must be a string containing a filter expression. Use `filter` when you want only to apply `function` to a subset of the documents in your database.
#### `context`
Use `context` to pass data to the `function` scope. By default a function only has access to the document it is editing.
### Example
```sh
curl \
-X POST 'MEILISEARCH_URL/indexes/INDEX_NAME/documents/edit' \
-H 'Content-Type: application/json' \
--data-binary '{
"function": "doc.title = `${doc.title.to_upper()}`"
}'
```
## Delete all documents
Delete all documents in the specified index.
### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/movies/documents'
```
```javascript JS
client.index('movies').deleteAllDocuments()
```
```python Python
client.index('movies').delete_all_documents()
```
```php PHP
$client->index('movies')->deleteAllDocuments();
```
```java Java
client.index("movies").deleteAllDocuments();
```
```ruby Ruby
client.index('movies').delete_all_documents
```
```go Go
client.Index("movies").DeleteAllDocuments()
```
```csharp C#
await client.Index("movies").DeleteAllDocumentsAsync();
```
```rust Rust
let task: TaskInfo = client
.index("movies")
.delete_all_documents()
.await
.unwrap();
```
```swift Swift
client.index("movies").deleteAllDocuments() { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').deleteAllDocuments();
```
#### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "documentDeletion",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Delete one document
Delete one document based on its unique id.
### Path parameters
| Name | Type | Description |
| :------------------- | :------------- | :-------------------------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
| **`document_id`** \* | String/Integer | [Document id](/learn/getting_started/primary_key#document-id) of the requested document |
### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/movies/documents/25684'
```
```javascript JS
client.index('movies').deleteDocument(25684)
```
```python Python
client.index('movies').delete_document(25684)
```
```php PHP
$client->index('movies')->deleteDocument(25684);
```
```java Java
client.index("movies").deleteDocument("25684");
```
```ruby Ruby
client.index('movies').delete_document(25684)
```
```go Go
client.Index("movies").DeleteDocument("25684")
```
```csharp C#
await client.Index("movies").DeleteOneDocumentAsync("25684");
```
```rust Rust
let task: TaskInfo = client
.index("movies")
.delete_document(25684)
.await
.unwrap();
```
```swift Swift
client.index("movies").deleteDocument("25684") { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').deleteDocument(25684);
```
#### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "documentDeletion",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Delete documents by filter
Delete a set of documents based on a filter.
### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
### Body
A filter expression written as a string or array of array of strings for the documents to be deleted.
`filter` will not work without first explicitly adding attributes to the [`filterableAttributes` list](/reference/api/settings#update-filterable-attributes). [Learn more about filters in our dedicated guide.](/learn/filtering_and_sorting/filter_search_results)
```
"filter": "rating > 3.5"
```
Sending an empty payload (`--data-binary '{}'`) will return a `bad_request` error.
### Example
```bash cURL
curl \
-X POST MEILISEARCH_URL/indexes/movies/documents/delete \
-H 'Content-Type: application/json' \
--data-binary '{
"filter": "genres = action OR genres = adventure"
}'
```
```javascript JS
client.index('movies').deleteDocuments({
filter: 'genres = action OR genres = adventure'
})
```
```python Python
client.index('movies').delete_documents(filter='genres=action OR genres=adventure')
```
```php PHP
$client->index('movies')->deleteDocuments(['filter' => 'genres = action OR genres = adventure']);
```
```java Java
String filter = "genres = action OR genres = adventure";
client.index("movies").deleteDocumentsByFilter(filter);
```
```ruby Ruby
client.index('movies').delete_documents(filter: 'genres = action OR genres = adventure')
```
```go Go
client.Index("movies").DeleteDocumentsByFilter("genres=action OR genres=adventure")
```
```csharp C#
await client.Index("movies").DeleteDocumentsAsync(new DeleteDocumentsQuery() { Filter = "genres = action OR genres = adventure" });
```
```rust Rust
let index = client.index("movies");
let task = DocumentDeletionQuery::new(&index)
.with_filter("genres = action OR genres = adventure")
.execute()
.await
.unwrap();
```
```dart Dart
await client.index('movies').deleteDocuments(
DeleteDocumentsQuery(
filterExpression: Meili.or([
Meili.attr('genres').eq(Meili.value('action')),
Meili.attr('genres').eq(Meili.value('adventure')),
]),
),
);
```
#### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "documentDeletion",
"enqueuedAt": "2023-05-15T08:38:48.024551Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Delete documents by batch
Delete a set of documents based on an array of document ids.
### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
### Body
An array of numbers containing the unique ids of the documents to be deleted.
```json
[23488, 153738, 437035, 363869]
```
### Example
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/documents/delete-batch' \
-H 'Content-Type: application/json' \
--data-binary '[
23488,
153738,
437035,
363869
]'
```
```javascript JS
client.index('movies').deleteDocuments([23488, 153738, 437035, 363869])
```
```python Python
client.index('movies').delete_documents([23488, 153738, 437035, 363869])
```
```php PHP
$client->index('movies')->deleteDocuments([23488, 153738, 437035, 363869]);
```
```java Java
client.index("movies").deleteDocuments(Arrays.asList(new String[]
{
"23488",
"153738",
"437035",
"363869"
}));
```
```ruby Ruby
client.index('movies').delete_documents([23488, 153738, 437035, 363869])
```
```go Go
client.Index("movies").DeleteDocuments([]string{
"23488",
"153738",
"437035",
"363869",
})
```
```csharp C#
await client.Index("movies").DeleteDocumentsAsync(new[] { "23488", "153738", "437035", "363869" });
```
```rust Rust
let task: TaskInfo = client
.index("movies")
.delete_documents(&[23488, 153738, 437035, 363869])
.await
.unwrap();
```
```dart Dart
await client.index('movies').deleteDocuments(
DeleteDocumentsQuery(
ids: [23488, 153738, 437035, 363869],
),
);
```
#### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "documentDeletion",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
# Facet search
Source: https://www.meilisearch.com/docs/reference/api/facet_search
The /facet-search route allows you to search for facet values.
export const RouteHighlighter = ({method, path}) =>
{method}
{path}
;
The `/facet-search` route allows you to search for facet values. Facet search supports [prefix search](/learn/engine/prefix) and [typo tolerance](/learn/relevancy/typo_tolerance_settings). The returned hits are sorted lexicographically in ascending order.
Meilisearch does not support facet search on numbers. Convert numeric facets to strings to make them searchable.
Internally, Meilisearch represents numbers as [`float64`](https://en.wikipedia.org/wiki/Double-precision_floating-point_format). This means they lack precision and can be represented in different ways, making it difficult to search facet values effectively.
## Perform a facet search
Search for a facet value within a given facet.
This endpoint will not work without first explicitly adding attributes to the [`filterableAttributes`](/reference/api/settings#update-filterable-attributes) list. [Learn more about facets in our dedicated guide.](/learn/filtering_and_sorting/search_with_facet_filters)
Meilisearch's facet search does not support multi-word facets and only considers the first term in the`facetQuery`.
For example, searching for `Jane` will return `Jane Austen`, but searching for `Austen` will not return `Jane Austen`.
### Body
| Name | Type | Default value | Description |
| ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`facetName`** \* | String | `null` | Facet name to search values on |
| **`facetQuery`** | String | `null` | Search query for a given facet value. If `facetQuery` isn't specified, Meilisearch returns all facet values for the searched facet, limited to 100 |
| **[`q`](/reference/api/search#query-q)** | String | `""` | Query string |
| **[`filter`](/reference/api/search#filter)** | [String\*](/learn/filtering_and_sorting/filter_expression_reference) | `null` | Filter queries by an attribute's value |
| **[`matchingStrategy`](/reference/api/search#matching-strategy)** | String | `"last"` | Strategy used to match query terms within documents |
| **[`attributesToSearchOn`](/reference/api/search##customize-attributes-to-search-on-at-search-time)** | Array of strings | `null` | Restrict search to the specified attributes |
| **`exhaustiveFacetCount`** | Boolean | `false` | Return an exhaustive count of facets, up to the limit defined by [`maxTotalHits`](/reference/api/settings#pagination) |
### Response
| Name | Type | Description |
| :--------------------- | :------ | :------------------------------------------------------ |
| **`facetHits.value`** | String | Facet value matching the `facetQuery` |
| **`facetHits.count`** | Integer | Number of documents with a facet value matching `value` |
| **`facetQuery`** | String | The original `facetQuery` |
| **`processingTimeMs`** | Number | Processing time of the query |
### Example
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/books/facet-search' \
-H 'Content-Type: application/json' \
--data-binary '{
"facetQuery": "fiction",
"facetName": "genres",
"filter": "rating > 3"
}'
```
```javascript JS
client.index('books').searchForFacetValues({
facetQuery: 'fiction',
facetName: 'genres'
filter: 'rating > 3'
})
```
```python Python
client.index('books').facet_search('genres', 'fiction', {
'filter': 'rating > 3'
})
```
```php PHP
$client->index('books')->facetSearch(
(new FacetSearchQuery())
->setFacetQuery('fiction')
->setFacetName('genres')
->setFilter(['rating > 3'])
);
```
```java Java
FacetSearchRequest fsr = FacetSearchRequest.builder().facetName("genres").facetQuery("fiction").filter(new String[]{"rating > 3"}).build();
client.index("books").facetSearch(fsr);
```
```ruby Ruby
client.index('books').facet_search('genres', 'fiction', filter: 'rating > 3')
```
```go Go
client.Index("books").FacetSearch(&meilisearch.FacetSearchRequest{
FacetQuery: "fiction",
FacetName: "genres",
Filter: "rating > 3",
})
```
```csharp C#
var query = new SearchFacetsQuery()
{
FacetQuery = "fiction",
Filter = "rating > 3"
};
await client.Index("books").FacetSearchAsync("genres", query);
```
```rust Rust
let res = client.index("books")
.facet_search("genres")
.with_facet_query("fiction")
.with_filter("rating > 3")
.execute()
.await
.unwrap();
```
```dart Dart
await client.index('books').facetSearch(
FacetSearchQuery(
facetQuery: 'fiction',
facetName: 'genres',
filter: 'rating > 3',
),
);
```
#### Response: `200 Ok`
```json
{
"facetHits": [
{
"value": "fiction",
"count": 7
}
],
"facetQuery": "fiction",
"processingTimeMs": 0
}
```
# Indexes
Source: https://www.meilisearch.com/docs/reference/api/indexes
The /indexes route allows you to create, manage, and delete your indexes.
export const RouteHighlighter = ({method, path}) =>
{method}
{path}
;
The `/indexes` route allows you to create, manage, and delete your indexes.
[Learn more about indexes](/learn/getting_started/indexes).
## Index object
```json
{
"uid": "movies",
"createdAt": "2022-02-10T07:45:15.628261Z",
"updatedAt": "2022-02-21T15:28:43.496574Z",
"primaryKey": "id"
}
```
| Name | Type | Default value | Description |
| :--------------- | :-------------- | :------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`uid`** | String | N/A | [Unique identifier](/learn/getting_started/indexes#index-uid) of the index. Once created, it cannot be changed |
| **`createdAt`** | String | N/A | Creation date of the index, represented in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. Auto-generated on index creation |
| **`updatedAt`** | String | N/A | Latest date of index update, represented in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. Auto-generated on index creation or update |
| **`primaryKey`** | String / `null` | `null` | [Primary key](/learn/getting_started/primary_key#primary-field) of the index. If not specified, Meilisearch [guesses your primary key](/learn/getting_started/primary_key#meilisearch-guesses-your-primary-key) from the first document you add to the index |
## List all indexes
List all indexes. Results can be paginated by using the `offset` and `limit` query parameters.
### Query parameters
| Query parameter | Description | Default value |
| :-------------- | :-------------------------- | :------------ |
| **`offset`** | Number of indexes to skip | `0` |
| **`limit`** | Number of indexes to return | `20` |
### Response
| Name | Type | Description |
| :------------ | :------ | :----------------------------------- |
| **`results`** | Array | An array of [indexes](#index-object) |
| **`offset`** | Integer | Number of indexes skipped |
| **`limit`** | Integer | Number of indexes returned |
| **`total`** | Integer | Total number of indexes |
### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes?limit=3'
```
```javascript JS
client.getIndexes({ limit: 3 })
```
```python Python
client.get_indexes({'limit': 3})
```
```php PHP
$client->getIndexes((new IndexesQuery())->setLimit(3));
```
```java Java
IndexesQuery query = new IndexesQuery().setLimit(3);
client.getIndexes(query);
```
```ruby Ruby
client.indexes(limit: 3)
```
```go Go
client.GetIndexes(&meilisearch.IndexesQuery{
Limit: 3,
})
```
```csharp C#
await client.GetAllIndexesAsync(new IndexesQuery { Limit = 3 });
```
```rust Rust
let mut indexes = IndexesQuery::new(&client)
.with_limit(3)
.execute()
.await
.unwrap();
```
```swift Swift
client.getIndexes { (result) in
switch result {
case .success(let indexes):
print(indexes)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.getIndexes(params: IndexesQuery(limit: 3));
```
#### Response: `200 Ok`
```json
{
"results": [
{
"uid": "books",
"createdAt": "2022-03-08T10:00:27.377346Z",
"updatedAt": "2022-03-08T10:00:27.391209Z",
"primaryKey": "id"
},
{
"uid": "meteorites",
"createdAt": "2022-03-08T10:00:44.518768Z",
"updatedAt": "2022-03-08T10:00:44.582083Z",
"primaryKey": "id"
},
{
"uid": "movies",
"createdAt": "2022-02-10T07:45:15.628261Z",
"updatedAt": "2022-02-21T15:28:43.496574Z",
"primaryKey": "id"
}
],
"offset": 0,
"limit": 3,
"total": 5
}
```
## Get one index
Get information about an index.
### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/movies'
```
```javascript JS
client.index('movies').getRawInfo()
```
```python Python
client.get_index('movies')
```
```php PHP
$client->index('movies')->fetchRawInfo();
```
```java Java
client.getIndex("movies");
```
```ruby Ruby
client.fetch_index('movies')
```
```go Go
client.GetIndex("movies")
```
```csharp C#
await client.GetIndexAsync("movies");
```
```rust Rust
let movies: Index = client
.get_index("movies")
.await
.unwrap();
```
```swift Swift
client.getIndex("movies") { (result) in
switch result {
case .success(let index):
print(index)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.getIndex('movies');
```
#### Response: `200 Ok`
```json
{
"uid": "movies",
"createdAt": "2022-02-10T07:45:15.628261Z",
"updatedAt": "2022-02-21T15:28:43.496574Z",
"primaryKey": "id"
}
```
## Create an index
Create an index.
### Body
| Name | Type | Default value | Description |
| :--------------- | :-------------- | :------------ | :--------------------------------------------------------------------------------------- |
| **`uid`** \* | String | N/A | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
| **`primaryKey`** | String / `null` | `null` | [`Primary key`](/learn/getting_started/primary_key#primary-field) of the requested index |
```json
{
"uid": "movies",
"primaryKey": "id"
}
```
### Example
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes' \
-H 'Content-Type: application/json' \
--data-binary '{
"uid": "movies",
"primaryKey": "id"
}'
```
```javascript JS
client.createIndex('movies', { primaryKey: 'id' })
```
```python Python
client.create_index('movies', {'primaryKey': 'id'})
```
```php PHP
$client->createIndex('movies', ['primaryKey' => 'id']);
```
```java Java
client.createIndex("movies", "id");
```
```ruby Ruby
client.create_index('movies', primary_key: 'id')
```
```go Go
client.CreateIndex(&meilisearch.IndexConfig{
Uid: "movies",
PrimaryKey: "id",
})
```
```csharp C#
TaskInfo task = await client.CreateIndexAsync("movies", "id");
```
```rust Rust
client.create_index("movies", Some("id"))
.await
.unwrap();
```
```swift Swift
client.createIndex(uid: "movies", primaryKey: "id") { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.createIndex('movies', primaryKey: 'id');
```
#### Response: `202 Accepted`
```json
{
"taskUid": 0,
"indexUid": "movies",
"status": "enqueued",
"type": "indexCreation",
"enqueuedAt": "2021-08-12T10:00:00.000000Z"
}
```
You can use the response's `taskUid` to [track the status of your request](/reference/api/tasks#get-one-task).
## Update an index
Update an index's [primary key](/learn/getting_started/primary_key#primary-key). You can freely update the primary key of an index as long as it contains no documents.
To change the primary key of an index that already contains documents, you must first delete all documents in that index. You may then change the primary key and index your dataset again.
### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
### Body
| Name | Type | Default value | Description |
| :------------------ | :-------------- | :------------ | :--------------------------------------------------------------------------------------- |
| **`primaryKey`** \* | String / `null` | N/A | [`Primary key`](/learn/getting_started/primary_key#primary-field) of the requested index |
| **`uid`** \* | String / `null` | N/A | New `uid` of the requested index |
### Example
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/indexes/movies' \
-H 'Content-Type: application/json' \
--data-binary '{ "primaryKey": "id" }'
```
```javascript JS
client.updateIndex('movies', { primaryKey: 'id' })
```
```python Python
client.index('movies').update(primary_key='id')
```
```php PHP
$client->updateIndex('movies', ['primaryKey' => 'id']);
```
```java Java
client.updateIndex("movies", "id");
```
```ruby Ruby
client.index('movies').update(primary_key: 'movie_id')
```
```go Go
client.Index("movies").UpdateIndex(&meilisearch.UpdateIndexRequestParams{
PrimaryKey: "id",
})
```
```csharp C#
TaskInfo task = await client.UpdateIndexAsync("movies", "id");
```
```rust Rust
let task = IndexUpdater::new("movies", &client)
.with_primary_key("movie_review_id")
.execute()
.await
.unwrap();
```
```swift Swift
client.index("movies").update(primaryKey: "id") { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').update(primaryKey: 'id');
```
#### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "indexUpdate",
"enqueuedAt": "2021-08-12T10:00:00.000000Z"
}
```
You can use the response's `taskUid` to [track the status of your request](/reference/api/tasks#get-one-task).
## Delete an index
Delete an index.
### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/movies'
```
```javascript JS
client.deleteIndex('movies')
```
```python Python
client.delete_index('movies')
// OR
client.index('movies').delete()
```
```php PHP
$client->deleteIndex('movies');
```
```java Java
client.deleteIndex("movies");
```
```ruby Ruby
client.delete_index('movies')
```
```go Go
client.DeleteIndex("movies")
// OR
client.Index("movies").Delete()
```
```csharp C#
await client.DeleteIndexAsync("movies");
```
```rust Rust
client.index("movies")
.delete()
.await
.unwrap();
```
```swift Swift
client.index("movies").delete { (result) in
switch result {
case .success:
print("Index deleted")
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').delete();
```
#### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "indexDeletion",
"enqueuedAt": "2021-08-12T10:00:00.000000Z"
}
```
You can use the response's `taskUid` to [track the status of your request](/reference/api/tasks#get-one-task).
## Swap indexes
Swap the documents, settings, and task history of two or more indexes. **You can only swap indexes in pairs.** A single request can swap as many index pairs as you wish.
Swapping indexes is an atomic transaction: **either all indexes in a request are successfully swapped, or none are.** You can swap multiple pairs of indexes with a single request. To do so, there must be one object for each pair of indexes to be swapped.
Swapping `indexA` and `indexB` will also replace every mention of `indexA` by `indexB` and vice-versa in the task history. `enqueued` tasks are left unmodified.
[To learn more about index swapping, refer to this short guide.](/learn/getting_started/indexes#swapping-indexes)
### Body
An array of objects with the following fields:
| Name | Type | Default value | Description |
| :------------ | :--------------- | :------------ | :------------------------------------------------- |
| **`indexes`** | Array of strings | N/A | Array of the two `indexUid`s to be swapped |
| **`rename`** | Boolean | `false` | If `true`, renames an index instead of swapping it |
Each `indexes` array must contain only two elements: the `indexUid`s of the two indexes to be swapped. Sending an empty array (`[]`) is valid, but no swap operation will be performed.
Use `rename: false` if you are swapping two existing indexes. Use `rename: true` if the second index in your array does not exist.
### Example
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/swap-indexes' \
-H 'Content-Type: application/json' \
--data-binary '[
{
"indexes": [
"indexA",
"indexB"
]
},
{
"indexes": [
"indexX",
"indexY"
]
}
]'
```
```javascript JS
client.swapIndexes([
{ 'indexes': ['indexA', 'indexB'] },
{ 'indexes': ['indexX', 'indexY'] }
])
```
```python Python
client.swap_indexes([{'indexes': ['indexA', 'indexB']}, {'indexes': ['indexX', 'indexY']}])
```
```php PHP
$client->swapIndexes([['indexA', 'indexB'], ['indexX', 'indexY']]);
```
```java Java
SwapIndexesParams[] params =
new SwapIndexesParams[] {
new SwapIndexesParams().setIndexes(new String[] {"indexA", "indexB"}),
new SwapIndexesParams().setIndexes(new String[] {"indexX", "indexY"})
};
TaskInfo task = client.swapIndexes(params);
```
```ruby Ruby
client.swap_indexes(['indexA', 'indexB'], ['indexX', 'indexY'])
```
```go Go
client.SwapIndexes([]SwapIndexesParams{
{Indexes: []string{"indexA", "indexB"}},
{Indexes: []string{"indexX", "indexY"}},
})
```
```csharp C#
await client.SwapIndexesAsync(new List { new IndexSwap("indexA", "indexB"), new IndexSwap("indexX", "indexY") } });
```
```rust Rust
client.swap_indexes([
&SwapIndexes {
indexes: (
"indexA".to_string(),
"indexB".to_string(),
),
}, &SwapIndexes {
indexes: (
"indexX".to_string(),
"indexY".to_string(),
),
}])
```
```swift Swift
let task = try await self.client.swapIndexes([
("indexA", "indexB"),
("indexX", "indexY")
])
```
```dart Dart
await client.swapIndexes([
SwapIndex(['indexA', 'indexB']),
SwapIndex(['indexX', 'indexY']),
]);
```
#### Response
```json
{
"taskUid": 3,
"indexUid": null,
"status": "enqueued",
"type": "indexSwap",
"enqueuedAt": "2021-08-12T10:00:00.000000Z"
}
```
Since `indexSwap` is a [global task](/learn/async/asynchronous_operations#global-tasks), the `indexUid` is always `null`.
You can use the response's `taskUid` to [track the status of your request](/reference/api/tasks#get-one-task).
# Keys
Source: https://www.meilisearch.com/docs/reference/api/keys
The /keys route allows you to create, manage, and delete API keys.
export const RouteHighlighter = ({method, path}) =>
{method}
{path}
;
The `/keys` route allows you to create, manage, and delete API keys. To use these endpoints, you must first [set the master key](/learn/security/basic_security). Once a master key is set, you can access these endpoints by supplying it in the header of the request, or using API keys that have access to the `keys.get`, `keys.create`, `keys.update`, or `keys.delete` actions.
Accessing the `/keys` route without setting a master key will throw a [`missing_master_key`](/reference/errors/error_codes#missing_master_key) error.
## Key object
```json
{
"name": "Default Search API Key",
"description": "Use it to search from the frontend code",
"key": "0a6e572506c52ab0bd6195921575d23092b7f0c284ab4ac86d12346c33057f99",
"uid": "74c9c733-3368-4738-bbe5-1d18a5fecb37",
"actions": [
"search"
],
"indexes": [
"*"
],
"expiresAt": null,
"createdAt": "2021-08-11T10:00:00Z",
"updatedAt": "2021-08-11T10:00:00Z"
}
```
### `name`
**Type**: String
**Default value**: `null`
**Description**: A human-readable name for the key
### `description`
**Type**: String
**Default value**: `null`
**Description**: A description for the key. You can add any important information about the key here
### `uid`
**Type**: String
**Default value**: N/A
**Description**: A [uuid v4](https://www.sohamkamani.com/uuid-versions-explained) to identify the API key. If not specified, it is automatically generated by Meilisearch
### `key`
**Type**: String
**Default value**: N/A
**Description**: An alphanumeric key value generated by Meilisearch by hashing the `uid` and the master key on API key creation. Used for authorization when [making calls to a protected Meilisearch instance](/learn/security/basic_security#sending-secure-api-requests-to-meilisearch)
This value is also used as the `{key}` path variable to [update](#update-a-key), [delete](#delete-a-key), or [get](#get-one-key) a specific key.
If the master key changes, all `key` values are automatically changed.
Custom API keys are deterministic: `key` is a SHA256 hash of the `uid` and master key. To reuse custom API keys, launch the new instance with the same master key and recreate your API keys with the same `uid`.
**You cannot reuse default API keys between instances.** Meilisearch automatically generates their `uid`s the first time you launch an instance.
### `actions`
**Type**: Array
**Default value**: N/A
**Description**: An array of API actions permitted for the key, represented as strings. API actions are only possible on authorized [`indexes`](#indexes). `["*"]` for all actions.
You can use `*` as a wildcard to access all endpoints for the `documents`, `indexes`, `tasks`, `settings`, `stats`, `webhooks`, and `dumps` actions. For example, `documents.*` gives access to all document actions.
For security reasons, we do not recommend creating keys that can perform all actions.
| Name | Description |
| :--------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`search`** | Provides access to both [`POST`](/reference/api/search#search-in-an-index-with-post) and [`GET`](/reference/api/search#search-in-an-index-with-get) search endpoints |
| **`documents.add`** | Provides access to the [add documents](/reference/api/documents#add-or-replace-documents) and [update documents](/reference/api/documents#add-or-update-documents) endpoints |
| **`documents.get`** | Provides access to the [get one document](/reference/api/documents#get-one-document), [get documents with POST](/reference/api/documents#get-documents-with-post), and [get documents with GET](/reference/api/documents#get-documents-with-get) endpoints |
| **`documents.delete`** | Provides access to the [delete one document](/reference/api/documents#delete-one-document), [delete all documents](/reference/api/documents#delete-all-documents), [batch delete](/reference/api/documents#delete-documents-by-batch), and [delete by filter](/reference/api/documents#delete-documents-by-filter) endpoints |
| **`indexes.create`** | Provides access to the [create index](/reference/api/indexes#create-an-index) endpoint |
| **`indexes.get`** | Provides access to the [get one index](/reference/api/indexes#get-one-index) and [list all indexes](/reference/api/indexes#list-all-indexes) endpoints. **Non-authorized `indexes` will be omitted from the response** |
| **`indexes.update`** | Provides access to the [update index](/reference/api/indexes#update-an-index) endpoint |
| **`indexes.delete`** | Provides access to the [delete index](/reference/api/indexes#delete-an-index) endpoint |
| **`indexes.swap`** | Provides access to the swap indexes endpoint. **Non-authorized `indexes` will not be swapped** |
| **`tasks.get`** | Provides access to the [get one task](/reference/api/tasks#get-one-task) and [get tasks](/reference/api/tasks#get-tasks) endpoints. **Tasks from non-authorized `indexes` will be omitted from the response** |
| **`tasks.cancel`** | Provides access to the [cancel tasks](/reference/api/tasks#cancel-tasks) endpoint. **Tasks from non-authorized `indexes` will not be canceled** |
| **`tasks.delete`** | Provides access to the [delete tasks](/reference/api/tasks#delete-tasks) endpoint. **Tasks from non-authorized `indexes` will not be deleted** |
| **`settings.get`** | Provides access to the [get settings](/reference/api/settings#get-settings) endpoint and equivalents for all subroutes |
| **`settings.update`** | Provides access to the [update settings](/reference/api/settings#update-settings) and [reset settings](/reference/api/settings#reset-settings) endpoints and equivalents for all subroutes |
| **`stats.get`** | Provides access to the [get stats of an index](/reference/api/stats#get-stats-of-an-index) endpoint and the [get stats of all indexes](/reference/api/stats#get-stats-of-all-indexes) endpoint. For the latter, **non-authorized `indexes` are omitted from the response** |
| **`dumps.create`** | Provides access to the [create dump](/reference/api/dump#create-a-dump) endpoint. **Not restricted by `indexes`** |
| **`snapshots.create`** | Provides access to the [create snapshot](/reference/api/snapshots#create-a-snapshot) endpoint. **Not restricted by `indexes`** |
| **`version`** | Provides access to the [get Meilisearch version](/reference/api/version#get-version-of-meilisearch) endpoint |
| **`keys.get`** | Provides access to the [get all keys](#get-all-keys) endpoint |
| **`keys.create`** | Provides access to the [create key](#create-a-key) endpoint |
| **`keys.update`** | Provides access to the [update key](#update-a-key) endpoint |
| **`keys.delete`** | Provides access to the [delete key](#delete-a-key) endpoint |
| **`network.get`** | Provides access to the [get the network object](/reference/api/network#get-the-network-object) endpoint |
| **`network.update`** | Provides access to the [update the network object](/reference/api/network#update-the-network-object) endpoint |
| **`chatCompletions`** | Provides access to the [chat completions endpoints](/reference/api/chats). Requires experimental feature to be enabled |
| **`webhooks.get`** | Provides access to the [get webhooks](/reference/api/webhooks#get-all-webhooks) endpoints |
| **`webhooks.create`** | Provides access to the [create webhooks](/reference/api/webhooks#create-a-webhook) endpoint |
| **`webhooks.update`** | Provides access to the [update webhooks](/reference/api/webhooks#update-a-webhook) endpoint |
| **`webhooks.delete`** | Provides access to the [delete webhooks](/reference/api/webhooks#delete-a-webhook) endpoint |
### `indexes`
**Type**: Array
**Default value**: N/A
**Description**: An array of indexes the key is authorized to act on. Use`["*"]` for all indexes. Only the key's [permitted actions](#actions) can be used on these indexes.
You can also use the `*` character as a wildcard by adding it at the end of a string. This allows an API key access to all index names starting with that string. For example, using `"indexes": ["movie*"]` will give the API key access to the `movies` and `movie_ratings` indexes.
### `expiresAt`
**Type**: String
**Default value**: N/A
**Description**: Date and time when the key will expire, represented in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. `null` if the key never expires
Once a key is past its `expiresAt` date, using it for API authorization will return an error.
### `createdAt`
**Type**: String
**Default value**: `null`
**Description**: Date and time when the key was created, represented in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format
### `updatedAt`
**Type**: String
**Default value**: `null`
**Description**: Date and time when the key was last updated, represented in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format
## Get all keys
Returns the 20 most recently created keys in a `results` array. **Expired keys are included in the response**, but deleted keys are not.
### Query parameters
Results can be paginated using the `offset` and `limit` query parameters.
| Query Parameter | Default Value | Description |
| :-------------- | :------------ | :----------------------- |
| **`offset`** | `0` | Number of keys to skip |
| **`limit`** | `20` | Number of keys to return |
### Response
| Name | Type | Description |
| :------------ | :------ | :------------------------------------- |
| **`results`** | Array | An array of [key objects](#key-object) |
| **`offset`** | Integer | Number of keys skipped |
| **`limit`** | Integer | Number of keys returned |
| **`total`** | Integer | Total number of API keys |
### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/keys?limit=3' \
-H 'Authorization: Bearer MASTER_KEY'
```
```javascript JS
client.getKeys({ limit: 3 })
```
```python Python
client.get_keys({'limit': 3})
```
```php PHP
$client->getKeys((new KeysQuery())->setLimit(3));
```
```java Java
KeysQuery query = new KeysQuery().setLimit(3);
client.getKeys(query);
```
```ruby Ruby
client.keys(limit: 3)
```
```go Go
client.GetKeys(&meilisearch.KeysQuery{
Limit: 3
});
```
```csharp C#
ResourceResults keyResult = await client.GetKeysAsync(new KeysQuery { Limit = 3 });
```
```rust Rust
let mut query = KeysQuery::new()
.with_limit(3)
.execute(&client)
.await
.unwrap();
```
```swift Swift
client.getKeys(params: KeysQuery(limit: 3)) { result in
switch result {
case .success(let keys):
print(keys)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.getKeys(params: KeysQuery(limit: 3));
```
#### Response: `200 Ok`
```json
{
"results": [
{
"name": null,
"description": "Manage documents: Products/Reviews API key",
"key": "d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4",
"uid": "6062abda-a5aa-4414-ac91-ecd7944c0f8d",
"actions": [
"documents.add",
"documents.delete"
],
"indexes": [
"prod*",
"reviews"
],
"expiresAt": "2021-12-31T23:59:59Z",
"createdAt": "2021-10-12T00:00:00Z",
"updatedAt": "2021-10-13T15:00:00Z"
},
{
"name": "Default Search API Key",
"description": "Use it to search from the frontend code",
"key": "0a6e572506c52ab0bd6195921575d23092b7f0c284ab4ac86d12346c33057f99",
"uid": "74c9c733-3368-4738-bbe5-1d18a5fecb37",
"actions": [
"search"
],
"indexes": [
"*"
],
"expiresAt": null,
"createdAt": "2021-08-11T10:00:00Z",
"updatedAt": "2021-08-11T10:00:00Z"
},
{
"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": "380689dd379232519a54d15935750cc7625620a2ea2fc06907cb40ba5b421b6f",
"uid": "20f7e4c4-612c-4dd1-b783-7934cc038213",
"actions": [
"*"
],
"indexes": [
"*"
],
"expiresAt": null,
"createdAt": "2021-08-11T10:00:00Z",
"updatedAt": "2021-08-11T10:00:00Z"
}
],
"offset": 0,
"limit": 3,
"total": 7
}
```
API keys are displayed in descending order based on their `createdAt` date. This means that the most recently created keys appear first.
## Get one key
Get information on the specified key. Attempting to use this endpoint with a non-existent or deleted key will result in [an error](/reference/errors/error_codes#api_key_not_found).
### Path parameters
A valid API `key` or `uid` is required.
| Name | Type | Description |
| :----------- | :----- | :------------------------------------------- |
| **`key`** \* | String | [`key`](#key) value of the requested API key |
| **`uid`** \* | String | [`uid`](#uid) of the requested API key |
### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/keys/6062abda-a5aa-4414-ac91-ecd7944c0f8d' \
-H 'Authorization: Bearer MASTER_KEY'
```
```javascript JS
client.getKey('6062abda-a5aa-4414-ac91-ecd7944c0f8d')
```
```python Python
client.get_key('6062abda-a5aa-4414-ac91-ecd7944c0f8d')
```
```php PHP
$client->getKey('6062abda-a5aa-4414-ac91-ecd7944c0f8d');
```
```java Java
client.getKey("6062abda-a5aa-4414-ac91-ecd7944c0f8d");
```
```ruby Ruby
client.key('6062abda-a5aa-4414-ac91-ecd7944c0f8d')
```
```go Go
client.GetKey("6062abda-a5aa-4414-ac91-ecd7944c0f8d")
```
```csharp C#
Key key = await client.GetKeyAsync("d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4");
```
```rust Rust
let key = client
.get_key("6062abda-a5aa-4414-ac91-ecd7944c0f8d")
.await
.unwrap();
```
```swift Swift
client.getKey(keyOrUid: "6062abda-a5aa-4414-ac91-ecd7944c0f8d") { result in
switch result {
case .success(let key):
print(key)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.getKey('6062abda-a5aa-4414-ac91-ecd7944c0f8d');
```
#### Response: `200 Ok`
```json
{
"name": null,
"description": "Add documents: Products API key",
"key": "d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4",
"uid": "6062abda-a5aa-4414-ac91-ecd7944c0f8d",
"actions": [
"documents.add"
],
"indexes": [
"products"
],
"expiresAt": "2021-11-13T00:00:00Z",
"createdAt": "2021-11-12T10:00:00Z",
"updatedAt": "2021-11-12T10:00:00Z"
}
```
For an explanation of these fields, see the [key object](#key-object).
## Create a key
Create an API key with the provided description, permissions, and expiration date.
### Body
| Name | Type | Default value | Description |
| :-------------------------------- | :----- | :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------- |
| **[`actions`](#actions)** \* | Array | N/A | A list of API actions permitted for the key. `["*"]` for all actions |
| **[`indexes`](#indexes)** \* | Array | N/A | An array of indexes the key is authorized to act on. `["*"]` for all indexes |
| **[`expiresAt`](#expiresat)** \* | String | N/A | Date and time when the key will expire, represented in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. `null` if the key never expires |
| **[`name`](#name)** | String | `null` | A human-readable name for the key |
| **[`uid`](#uid)** | String | N/A | A [uuid v4](https://www.sohamkamani.com/uuid-versions-explained) to identify the API key. If not specified, it is generated by Meilisearch |
| **[`description`](#description)** | String | `null` | An optional description for the key |
### Example
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/keys' \
-H 'Authorization: Bearer MASTER_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"description": "Add documents: Products API key",
"actions": ["documents.add"],
"indexes": ["products"],
"expiresAt": "2042-04-02T00:42:42Z"
}'
```
```javascript JS
client.createKey({
description: 'Add documents: Products API key',
actions: ['documents.add'],
indexes: ['products'],
expiresAt: '2021-11-13T00:00:00Z'
})
```
```python Python
client.create_key(options={
'description': 'Add documents: Products API key',
'actions': ['documents.add'],
'indexes': ['products'],
'expiresAt': '2042-04-02T00:42:42Z'
})
```
```php PHP
$client->createKey([
'description' => 'Add documents: Products API key',
'actions' => ['documents.add'],
'indexes' => ['products'],
'expiresAt' => '2042-04-02T00:42:42Z',
]);
```
```java Java
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
Date dateParsed = format.parse("2042-04-02T00:42:42Z");
Key keyInfo = new Key();
keyInfo.setDescription("Add documents: Products API key");
keyInfo.setActions(new String[] {"documents.add"});
keyInfo.setIndexes(new String[] {"products"});
keyInfo.setExpiresAt(dateParsed);
client.createKey(keyInfo);
```
```ruby Ruby
client.create_key(
description: 'Add documents: Products API key',
actions: ['documents.add'],
indexes: ['products'],
expires_at: '2042-04-02T00:42:42Z'
)
```
```go Go
client.CreateKey(&meilisearch.Key{
Description: "Add documents: Products API key",
Actions: []string{"documents.add"},
Indexes: []string{"products"},
ExpiresAt: time.Date(2042, time.April, 02, 0, 42, 42, 0, time.UTC),
})
```
```csharp C#
Key keyOptions = new Key
{
Description = "Add documents: Products API key",
Actions = new KeyAction[] { KeyAction.DocumentsAdd },
Indexes = new string[] { "products" },
ExpiresAt = DateTime.Parse("2042-04-02T00:42:42Z")
};
Key createdKey = await this.client.CreateKeyAsync(keyOptions);
```
```rust Rust
let mut key_options = KeyBuilder::new();
key_options
.with_name("Add documents: Products API key")
.with_action(Action::DocumentsAdd)
.with_expires_at(time::macros::datetime!(2042 - 04 - 02 00:42:42 UTC))
.with_index("products");
let new_key = client
.create_key(key_options)
.await
.unwrap();
```
```swift Swift
let keyParams = KeyParams(
description: "Add documents: Products API key",
actions: ["documents.add"],
indexes: ["products"],
expiresAt: "2042-04-02T00:42:42Z"
)
client.createKey(keyParams) { result in
switch result {
case .success(let key):
print(key)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.createKey(
description: 'Add documents: Products API key',
actions: ['documents.add'],
indexes: ['products'],
expiresAt: DateTime(2042, 04, 02));
```
#### Response: `201 Created`
```json
{
"name": null,
"description": "Manage documents: Products/Reviews API key",
"key": "d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4",
"uid": "6062abda-a5aa-4414-ac91-ecd7944c0f8d",
"actions": [
"documents.add"
],
"indexes": [
"products"
],
"expiresAt": "2021-11-13T00:00:00Z",
"createdAt": "2021-11-12T10:00:00Z",
"updatedAt": "2021-11-12T10:00:00Z"
}
```
## Update a key
Update the `name` and `description` of an API key.
Updates to keys are **partial**. This means you should provide only the fields you intend to update, as any fields not present in the payload will remain unchanged.
### Path parameters
A valid API `key` or `uid` is required.
| Name | Type | Description |
| :----------- | :----- | :------------------------------------------- |
| **`key`** \* | String | [`key`](#key) value of the requested API key |
| **`uid`** \* | String | [`uid`](#uid) of the requested API key |
### Body
| Name | Type | Default value | Description |
| :-------------------------------- | :----- | :------------ | :---------------------------------- |
| **[`name`](#name)** | String | `null` | A human-readable name for the key |
| **[`description`](#description)** | String | `null` | An optional description for the key |
### Example
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/keys/6062abda-a5aa-4414-ac91-ecd7944c0f8d' \
-H 'Authorization: Bearer MASTER_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"name": "Products/Reviews API key",
"description": "Manage documents: Products/Reviews API key"
}'
```
```javascript JS
client.updateKey('6062abda-a5aa-4414-ac91-ecd7944c0f8d', {
name: 'Products/Reviews API key',
description: 'Manage documents: Products/Reviews API key',
})
```
```python Python
client.update_key(key_or_uid='6062abda-a5aa-4414-ac91-ecd7944c0f8d',
options={
'name': 'Products/Reviews API key',
'description': 'Manage documents: Products/Reviews API key'
})
```
```php PHP
$client->updateKey('6062abda-a5aa-4414-ac91-ecd7944c0f8d',
[
'name' => 'Products/Reviews API key',
'description' => 'Manage documents: Products/Reviews API key'
]);
```
```java Java
KeyUpdate keyChanges = new KeyUpdate();
keyChanges.setName("Products/Reviews API key");
keyChanges.setDescription("Manage documents: Products/Reviews API key");
client.updateKey("6062abda-a5aa-4414-ac91-ecd7944c0f8d", keyChanges);
```
```ruby Ruby
client.update_key(
'6062abda-a5aa-4414-ac91-ecd7944c0f8d',
{
description: 'Manage documents: Products/Reviews API key',
name: 'Products/Reviews API key'
}
)
```
```go Go
client.UpdateKey("6062abda-a5aa-4414-ac91-ecd7944c0f8d", &meilisearch.Key{
Description: "Manage documents: Products/Reviews API key",
Actions: []string{"documents.add", "document.delete"},
Indexes: []string{"products", "reviews"},
ExpiresAt: time.Date(2042, time.April, 02, 0, 42, 42, 0, time.UTC),
})
```
```csharp C#
await client.UpdateKeyAsync(
"6062abda-a5aa-4414-ac91-ecd7944c0f8d",
description: "Manage documents: Products/Reviews API key",
name: "Products/Reviews API key"
)
```
```rust Rust
let mut key = client
.get_key("6062abda-a5aa-4414-ac91-ecd7944c0f8d")
.await
.unwrap();
key
.with_description("Manage documents: Products/Reviews API key".to_string())
.with_name("Products/Reviews API key".to_string())
.update(&client)
.await
.unwrap();
```
```swift Swift
let keyParams = KeyUpdateParams(
description: "Manage documents: Products/Reviews API key",
name: "Products/Reviews API key"
)
client.updateKey(keyOrUid: "6062abda-a5aa-4414-ac91-ecd7944c0f8d", keyParams: keyParams) { result in
switch result {
case .success(let key):
print(key)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.updateKey(
'6062abda-a5aa-4414-ac91-ecd7944c0f8d',
description: 'Manage documents: Products/Reviews API key',
name: 'Products/Reviews API key',
);
```
#### Response: `200 Ok`
```json
{
"name": "Products/Reviews API key",
"description": "Manage documents: Products/Reviews API key",
"key": "d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4",
"uid": "6062abda-a5aa-4414-ac91-ecd7944c0f8d",
"actions": [
"documents.add",
"documents.delete"
],
"indexes": [
"products",
"reviews"
],
"expiresAt": "2021-12-31T23:59:59Z",
"createdAt": "2021-10-12T00:00:00Z",
"updatedAt": "2021-10-13T15:00:00Z"
}
```
## Delete a key
Delete the specified API key.
### Path parameters
A valid API `key` or `uid` is required.
| Name | Type | Description |
| :----------- | :----- | :------------------------------------------- |
| **`key`** \* | String | [`key`](#key) value of the requested API key |
| **`uid`** \* | String | [`uid`](#uid) of the requested API key |
### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/keys/6062abda-a5aa-4414-ac91-ecd7944c0f8d' \
-H 'Authorization: Bearer MASTER_KEY'
```
```javascript JS
client.deleteKey('6062abda-a5aa-4414-ac91-ecd7944c0f8d')
```
```python Python
client.delete_key('6062abda-a5aa-4414-ac91-ecd7944c0f8d')
```
```php PHP
$client->deleteKey('6062abda-a5aa-4414-ac91-ecd7944c0f8d');
```
```java Java
client.deleteKey("6062abda-a5aa-4414-ac91-ecd7944c0f8d")
```
```ruby Ruby
client.delete_key('6062abda-a5aa-4414-ac91-ecd7944c0f8d')
```
```go Go
client.DeleteKey("6062abda-a5aa-4414-ac91-ecd7944c0f8d")
```
```csharp C#
client.DeleteKeyAsync("6062abda-a5aa-4414-ac91-ecd7944c0f8d")
```
```rust Rust
let key = client
.get_key("6062abda-a5aa-4414-ac91-ecd7944c0f8d")
.await
.unwrap();
client
.delete_key(&key)
.await?;
```
```swift Swift
client.deleteKey(keyOrUid: "6062abda-a5aa-4414-ac91-ecd7944c0f8d") { result in
switch result {
case .success:
print("success")
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.deleteKey('6062abda-a5aa-4414-ac91-ecd7944c0f8d');
```
#### Response: `204 No Content`
# Multi-search
Source: https://www.meilisearch.com/docs/reference/api/multi_search
The /multi-search route allows you to perform multiple search queries on one or more indexes.
export const NoticeTag = ({label}) =>
{label}
;
export const RouteHighlighter = ({method, path}) =>
{method}
{path}
;
The `/multi-search` route allows you to perform multiple search queries on one or more indexes by bundling them into a single HTTP request. Multi-search is also known as federated search.
## Perform a multi-search
Bundle multiple search queries in a single API request. Use this endpoint to search through multiple indexes at once.
### Body
| Name | Type | Description |
| :--------------- | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`federation`** | Object | If present and not `null`, returns a single list merging all search results across all specified queries |
| **`queries`** | Array of objects | Contains the list of search queries to perform. The [`indexUid`](/learn/getting_started/indexes#index-uid) search parameter is required, all other parameters are optional |
If Meilisearch encounters an error when handling any of the queries in a multi-search request, it immediately stops processing the request and returns an error message. The returned message will only address the first error encountered.
#### `federation`
Use `federation` to receive a single list with all search results from all specified queries, in descending ranking score order. This is called federated search.
`federation` may optionally contain the following parameters:
| Parameter | Type | Default value | Description |
| :------------------------------------------- | :--------------- | :------------ | :-------------------------------------------------- |
| **[`offset`](/reference/api/search#offset)** | Integer | `0` | Number of documents to skip |
| **[`limit`](/reference/api/search#limit)** | Integer | `20` | Maximum number of documents returned |
| **[`facetsByIndex`](#facetsbyindex)** | Object of arrays | `null` | Display facet information for the specified indexes |
| **[`mergeFacets`](#mergefacets)** | Object | `null` | Display facet information for the specified indexes |
If `federation` is missing or `null`, Meilisearch returns a list of multiple search result objects, with each item from the list corresponding to a search query in the request.
##### `facetsByIndex`
`facetsByIndex` must be an object. Its keys must correspond to indexes in your Meilisearch project. Each key must be associated with an array of attributes in the filterable attributes list of that index:
```json
"facetsByIndex": {
"INDEX_A": ["ATTRIBUTE_X", "ATTRIBUTE_Y"],
"INDEX_B": ["ATTRIBUTE_Z"]
}
```
When you specify `facetsByIndex`, multi-search responses include an extra `facetsByIndex` field. The response's `facetsByIndex` is an object with one field for each queried index:
```json
{
"hits" [ … ],
…
"facetsByIndex": {
"INDEX_A": {
"distribution": {
"ATTRIBUTE_X": {
"KEY": ,
"KEY": ,
…
},
"ATTRIBUTE_Y": {
"KEY": ,
…
}
},
"stats": {
"KEY": {
"min": ,
"max":
}
}
},
"INDEX_B": {
…
}
}
}
```
##### `mergeFacets`
`mergeFacets` must be an object and may contain the following fields:
* `maxValuesPerFacet`: must be an integer. When specified, indicates the maximum number of returned values for a single facet. Defaults to the value assigned to [the `maxValuesPerFacet` index setting](/reference/api/settings#faceting)
When both `facetsByIndex` and `mergeFacets` are present and not null, facet information included in multi-search responses is merged across all queried indexes. Instead of `facetsByIndex`, the response includes two extra fields: `facetDistribution` and `facetStats`:
```json
{
"hits": [ … ],
…
"facetDistribution": {
"ATTRIBUTE": {
"VALUE": ,
"VALUE":
}
},
"facetStats": {
"ATTRIBUTE": {
"min": ,
"max":
}
}
}
```
##### Merge algorithm for federated searches
Federated search's merged results are returned in decreasing ranking score. To obtain the final list of results, Meilisearch compares with the following procedure:
1. Detailed ranking scores are normalized in the following way for both hits:
1. Consecutive relevancy scores (related to the rules `words`, `typo`, `attribute`, `exactness` or `vector`) are grouped in a single score for each hit
2. `sort` and `geosort` score details remain unchanged
2. Normalized detailed ranking scores are compared lexicographically for both hits:
1. If both hits have a relevancy score, then the bigger score wins. If it is a tie, move to next step
2. If one result has a relevancy score or a (geo)sort score, Meilisearch picks it
3. If both results have a sort or geosort score in the same sorting direction, then Meilisearch compares the values according to the common sort direction. The result with the value that must come first according to the common sort direction wins. If it is a tie, go to the next step
4. Compare the global ranking scores of both hits to determine which comes first, ignoring any sorting or geosorting
5. In the case of a perfect tie, documents from the query with the lowest rank in the `queries` array are preferred.
Meilisearch considers two documents the same if:
1. They come from the same index
2. And their primary key is the same
There is no way to specify that two documents should be treated as the same across multiple indexes.
#### `queries`
`queries` must be an array of objects. Each object may contain the following search parameters:
| Search parameter | Type | Default value | Description |
| :--------------------------------------------------------------------------------------------------- | :--------------- | :------------ | :--------------------------------------------------------------------- |
| **[`federationOptions`](#federationoptions)** | Object | `null` | Configure federation settings for a specific query |
| **[`indexUid`](/learn/getting_started/indexes#index-uid)** | String | N/A | `uid` of the requested index |
| **[`q`](/reference/api/search#query-q)** | String | `""` | Query string |
| **[`offset`](/reference/api/search#offset)** | Integer | `0` | Number of documents to skip |
| **[`limit`](/reference/api/search#limit)** | Integer | `20` | Maximum number of documents returned |
| **[`hitsPerPage`](/reference/api/search#number-of-results-per-page)** | Integer | `1` | Maximum number of documents returned for a page |
| **[`page`](/reference/api/search#page)** | Integer | `1` | Request a specific page of results |
| **[`filter`](/reference/api/search#filter)** | String | `null` | Filter queries by an attribute's value |
| **[`facets`](/reference/api/search#facets)** | Array of strings | `null` | Display the count of matches per facet |
| **[`distinct`](/reference/api/search#distinct-attributes-at-search-time)** | String | `null` | Restrict search to documents with unique values of specified attribute |
| **[`attributesToRetrieve`](/reference/api/search#attributes-to-retrieve)** | Array of strings | `["*"]` | Attributes to display in the returned documents |
| **[`attributesToCrop`](/reference/api/search#attributes-to-crop)** | Array of strings | `null` | Attributes whose values have to be cropped |
| **[`cropLength`](/reference/api/search#crop-length)** | Integer | `10` | Maximum length of cropped value in words |
| **[`cropMarker`](/reference/api/search#crop-marker)** | String | `"…"` | String marking crop boundaries |
| **[`attributesToHighlight`](/reference/api/search#attributes-to-highlight)** | Array of strings | `null` | Highlight matching terms contained in an attribute |
| **[`highlightPreTag`](/reference/api/search#highlight-tags)** | String | `""` | String inserted at the start of a highlighted term |
| **[`highlightPostTag`](/reference/api/search#highlight-tags)** | String | `""` | String inserted at the end of a highlighted term |
| **[`showMatchesPosition`](/reference/api/search#show-matches-position)** | Boolean | `false` | Return matching terms location |
| **[`sort`](/reference/api/search#sort)** | Array of strings | `null` | Sort search results by an attribute's value |
| **[`matchingStrategy`](/reference/api/search#matching-strategy)** | String | `last` | Strategy used to match query terms within documents |
| **[`showRankingScore`](/reference/api/search#ranking-score)** | Boolean | `false` | Display the global ranking score of a document |
| **[`showRankingScoreDetails`](/reference/api/search#ranking-score-details)** | Boolean | `false` | Adds a detailed global ranking score field |
| **[`rankingScoreThreshold`](/reference/api/search#ranking-score-threshold)** | Number | `null` | Excludes results with low ranking scores |
| **[`attributesToSearchOn`](/reference/api/search#customize-attributes-to-search-on-at-search-time)** | Array of strings | `["*"]` | Restrict search to the specified attributes |
| **[`hybrid`](/reference/api/search#hybrid-search)** | Object | `null` | Return results based on query keywords and meaning |
| **[`vector`](/reference/api/search#vector)** | Array of numbers | `null` | Search using a custom query vector |
| **[`retrieveVectors`](/reference/api/search#display-_vectors-in-response)** | Boolean | `false` | Return document vector data |
| **[`locales`](/reference/api/search#query-locales)** | Array of strings | `null` | Explicitly specify languages used in a query |
Unless otherwise noted, search parameters for multi-search queries function exactly like [search parameters for the `/search` endpoint](/reference/api/search#search-parameters).
##### `limit`, `offset`, `hitsPerPage` and `page`
These options are not compatible with federated searches.
##### `federationOptions`
`federationOptions` must be an object. It accepts the following parameters:
* `weight`: serves as a multiplicative factor to ranking scores of search results in this specific query. If \< `1.0`, the hits from this query are less likely to appear in the final results list. If > `1.0`, the hits from this query are more likely to appear in the final results list. Must be a positive floating-point number. Defaults to `1.0`
* `remote` : indicates the remote instance where Meilisearch will perform the query. Must be a string corresponding to a [remote object](/reference/api/network). Defaults to `null`
### Response
The response to `/multi-search` queries may take different shapes depending on the type of query you're making.
#### Non-federated multi-search requests
| Name | Type | Description |
| :------------ | :--------------- | :--------------------------------------------------------------------- |
| **`results`** | Array of objects | Results of the search queries in the same order they were requested in |
Each search result object is composed of the following fields:
| Name | Type | Description |
| :----------------------- | :--------------- | :------------------------------------------------------------------------------- |
| **`indexUid`** | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
| **`hits`** | Array of objects | Results of the query |
| **`offset`** | Number | Number of documents skipped |
| **`limit`** | Number | Number of documents to take |
| **`estimatedTotalHits`** | Number | Estimated total number of matches |
| **`totalHits`** | Number | Exhaustive total number of matches |
| **`totalPages`** | Number | Exhaustive total number of search result pages |
| **`hitsPerPage`** | Number | Number of results on each page |
| **`page`** | Number | Current search results page |
| **`facetDistribution`** | Object | **[Distribution of the given facets](/reference/api/search#facetdistribution)** |
| **`facetStats`** | Object | [The numeric `min` and `max` values per facet](/reference/api/search#facetstats) |
| **`processingTimeMs`** | Number | Processing time of the query |
| **`query`** | String | Query originating the response |
#### Federated multi-search requests
Federated search requests return a single object and the following fields:
| Name | Type | Description |
| :----------------------- | :--------------- | :-------------------------------------------------------------- |
| **`hits`** | Array of objects | Results of the query |
| **`offset`** | Number | Number of documents skipped |
| **`limit`** | Number | Number of documents to take |
| **`estimatedTotalHits`** | Number | Estimated total number of matches |
| **`processingTimeMs`** | Number | Processing time of the query |
| **`facetsByIndex`** | Object | [Data for facets present in the search results](#facetsbyindex) |
| **`facetDistribution`** | Object | [Distribution of the given facets](#mergefacets) |
| **`facetStats`** | Object | [The numeric `min` and `max` values per facet](#mergefacets) |
| **`remoteErrors`** | Object | Indicates which remote requests failed and why |
Each result in the `hits` array contains an additional `_federation` field with the following fields:
| Name | Type | Description |
| :------------------------- | :----- | :---------------------------------------------------------------------------------- |
| **`indexUid`** | String | Index of origin for this document |
| **`queriesPosition`** | Number | Array index number of the query in the request's `queries` array |
| **`remote`** | String | Remote instance of origin for this document |
| **`weightedRankingScore`** | Number | The product of the \_rankingScore of the hit and the weight of the query of origin. |
### Example
#### Non-federated multi-search
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/multi-search' \
-H 'Content-Type: application/json' \
--data-binary '{
"queries": [
{
"indexUid": "movies",
"q": "pooh",
"limit": 5
},
{
"indexUid": "movies",
"q": "nemo",
"limit": 5
},
{
"indexUid": "movie_ratings",
"q": "us"
}
]
}'
```
```javascript JS
client.multiSearch({ queries: [
{
indexUid: 'movies',
q: 'pooh',
limit: 5,
},
{
indexUid: 'movies',
q: 'nemo',
limit: 5,
},
{
indexUid: 'movie_ratings',
q: 'us',
},
]})
```
```python Python
client.multi_search(
[
{'indexUid': 'movies', 'q': 'pooh', 'limit': 5},
{'indexUid': 'movies', 'q': 'nemo', 'limit': 5},
{'indexUid': 'movie_ratings', 'q': 'us'}
]
)
```
```php PHP
$client->multiSearch([
(new SearchQuery())
->setIndexUid('movies')
->setQuery('pooh')
->setLimit(5),
(new SearchQuery())
->setIndexUid('movies')
->setQuery('nemo')
->setLimit(5),
(new SearchQuery())
->setIndexUid('movie_ratings')
->setQuery('us')
]);
```
```java Java
MultiSearchRequest multiSearchRequest = new MultiSearchRequest();
multiIndexSearch.addQuery(new IndexSearchRequest("movies").setQuery("pooh").setLimit(5));
multiIndexSearch.addQuery(new IndexSearchRequest("movies").setQuery("nemo").setLimit(5));
multiIndexSearch.addQuery(new IndexSearchRequest("movie_ratings").setQuery("us"));
client.multiSearch(multiSearchRequest);
```
```ruby Ruby
client.multi_search([
{ index_uid: 'books', q: 'prince' },
{ index_uid: 'movies', q: 'pooh', limit: 5 }
{ index_uid: 'movies', q: 'nemo', limit: 5 }
{ index_uid: 'movie_ratings', q: 'us' }
])
```
```go Go
client.MultiSearch(&MultiSearchRequest{
Queries: []SearchRequest{
{
IndexUID: "movies",
Query: "pooh",
Limit: 5,
},
{
IndexUID: "movies",
Query: "nemo",
Limit: 5,
},
{
IndexUID: "movie_ratings",
Query: "us",
},
},
})
```
```csharp C#
await client.MultiSearchAsync(new MultiSearchQuery()
{
Queries = new System.Collections.Generic.List()
{
new SearchQuery() {
IndexUid = "movies",
Q = "booh",
Limit = 5
},
new SearchQuery() {
IndexUid = "movies",
Q = "nemo",
Limit = 5
},
new SearchQuery() {
IndexUid = "movie_ratings",
Q = "us",
},
}
});
```
```rust Rust
let movie = client.index("movie");
let movie_ratings = client.index("movie_ratings");
let search_query_1 = SearchQuery::new(&movie)
.with_query("pooh")
.with_limit(5)
.build();
let search_query_2 = SearchQuery::new(&movie)
.with_query("nemo")
.with_limit(5)
.build();
let search_query_3 = SearchQuery::new(&movie_ratings)
.with_query("us")
.build();
let response = client
.multi_search()
.with_search_query(search_query_1)
.with_search_query(search_query_2)
.with_search_query(search_query_3)
.execute::()
.await
.unwrap();
```
```dart Dart
await client.multiSearch(MultiSearchQuery(queries: [
IndexSearchQuery(query: 'pooh', indexUid: 'movies', limit: 5),
IndexSearchQuery(query: 'nemo', indexUid: 'movies', limit: 5),
IndexSearchQuery(query: 'us', indexUid: 'movies_ratings'),
]));
```
##### Response: `200 Ok`
```json
{
"results": [
{
"indexUid": "movies",
"hits": [
{
"id": 13682,
"title": "Pooh's Heffalump Movie",
…
},
…
],
"query": "pooh",
"processingTimeMs": 26,
"limit": 5,
"offset": 0,
"estimatedTotalHits": 22
},
{
"indexUid": "movies",
"hits": [
{
"id": 12,
"title": "Finding Nemo",
…
},
…
],
"query": "nemo",
"processingTimeMs": 5,
"limit": 5,
"offset": 0,
"estimatedTotalHits": 11
},
{
"indexUid": "movie_ratings",
"hits": [
{
"id": "Us",
"director": "Jordan Peele",
…
}
],
"query": "Us",
"processingTimeMs": 0,
"limit": 20,
"offset": 0,
"estimatedTotalHits": 1
}
]
}
```
#### Federated multi-search
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/multi-search' \
-H 'Content-Type: application/json' \
--data-binary '{
"federation": {},
"queries": [
{
"indexUid": "movies",
"q": "batman"
},
{
"indexUid": "comics",
"q": "batman"
}
]
}'
```
```javascript JS
client.multiSearch({
federation: {},
queries: [
{
indexUid: 'movies',
q: 'batman',
},
{
indexUid: 'comics',
q: 'batman',
},
]
})
```
```python Python
client.multi_search(
[{"indexUid": "movies", "q": "batman"}, {"indexUid": "comics", "q": "batman"}],
{}
)
```
```php PHP
$client->multiSearch([
(new SearchQuery())
->setIndexUid('movies'))
->setQuery('batman'),
(new SearchQuery())
->setIndexUid('comics')
->setQuery('batman'),
],
(new MultiSearchFederation())
);
```
```ruby Ruby
client.multi_search(
queries: [{ index_uid: 'movies', q: 'batman' }, { index_uid: 'comics', q: 'batman' }],
federation: {}
)
```
##### Response: `200 Ok`
```json
{
"hits": [
{
"id": 42,
"title": "Batman returns",
"overview": …,
"_federation": {
"indexUid": "movies",
"queriesPosition": 0
}
},
{
"comicsId": "batman-killing-joke",
"description": …,
"title": "Batman: the killing joke",
"_federation": {
"indexUid": "comics",
"queriesPosition": 1
}
},
…
],
"processingTimeMs": 0,
"limit": 20,
"offset": 0,
"estimatedTotalHits": 2,
"semanticHitCount": 0
}
```
#### Remote federated multi-search
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/multi-search' \
-H 'Content-Type: application/json' \
--data-binary '{
"federation": {},
"queries": [
{
"indexUid": "movies",
"q": "batman",
"federationOptions": {
"remote": "ms-00"
}
},
{
"indexUid": "movies",
"q": "batman",
"federationOptions": {
"remote": "ms-01"
}
}
]
}'
```
##### Response: `200 Ok`
```json
{
"hits": [
{
"id": 42,
"title": "Batman returns",
"overview": …,
"_federation": {
"indexUid": "movies",
"queriesPosition": 0,
"weightedRankingScore": 1.0,
"remote": "ms-01"
}
},
{
"id": 87,
"description": …,
"title": "Batman: the killing joke",
"_federation": {
"indexUid": "movies",
"queriesPosition": 1,
"weightedRankingScore": 0.9848484848484849,
"remote": "ms-00"
}
},
…
],
"processingTimeMs": 35,
"limit": 5,
"offset": 0,
"estimatedTotalHits": 111,
"remoteErrors": {
"ms-02": {
"message": "error sending request",
"code": "proxy_could_not_send_request",
"type": "system",
"link": "https://docs.meilisearch.com/errors#proxy_could_not_make_request"
}
}
}
```
# Network
Source: https://www.meilisearch.com/docs/reference/api/network
Use the `/network` route to create a network of Meilisearch instances.
export const NoticeTag = ({label}) =>
{label}
;
export const RouteHighlighter = ({method, path}) =>
{method}
{path}
;
Use the `/network` route to create a network of Meilisearch instances. This is particularly useful when used together with federated search to implement horizontal database partition strategies such as sharding.
This is an experimental feature. Use the Meilisearch Cloud UI or the experimental features endpoint to activate it:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{
"network": true
}'
```
If an attribute is both:
* not on the `displayedAttributes` list
* present on the `sortableAttributes`
It is possible its value becomes publicly accessible via 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.
## The network object
```json
{
"self": "ms-00",
"sharding": false,
"remotes": {
"ms-00": {
"url": "http://ms-1235.example.meilisearch.io",
"searchApiKey": "Ecd1SDDi4pqdJD6qYLxD3y7VZAEb4d9j6LJgt4d6xas",
"writeApiKey": "O2OaIHgwGuHNx9duH6kSe1YJ55Bh0dXvLhbr8FQVvr3vRVViBO"
},
"ms-01": {
"url": "http://ms-4242.example.meilisearch.io",
"searchApiKey": "hrVu-OMcjPGElK7692K7bwriBoGyHXTMvB5NmZkMKqQ",
"writeApiKey": "bd1ldDoFlfyeoFDe8f3GVNiE8AHX86chmFuzOW7nWYUbPa7ww3"
}
}
}
```
### `self`
**Type**: String
**Default value**: `null`
**Description**: A string indicating the name of the current instance
### `sharding`
**Type**: Boolean
**Default value**: `false`
**Description**: A boolean indicating whether sharding should be enabled on the network
### `remotes`
**Type**: Object
**Default value**: `{}`
**Description**: An object containing [remote objects](#the-remote-object). The key of each remote object indicates the name of the remote instance
#### The remote object
```json
"ms-00": {
"url": "http://ms-1235.example.meilisearch.io",
"searchApiKey": "Ecd1SDDi4pqdJD6qYLxD3y7VZAEb4d9j6LJgt4d6xas",
"writeApiKey": "O2OaIHgwGuHNx9duH6kSe1YJ55Bh0dXvLhbr8FQVvr3vRVViBO"
}
```
##### `url`
**Type**: String
**Default value**: `null`
**Description**: URL indicating the address of a Meilisearch instance. This URL does not need to be public, but must be accessible to all instances in the network. Required
##### `searchApiKey`
**Type**: String
**Default value**: `null`
**Description**: An API key with search permissions
##### `writeApiKey`
**Type**: String
**Default value**: `null`
**Description**: An API key with `documents.*` permissions
## Get the network object
Returns the current value of the instance's network object.
### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/network'
```
#### Response: `200 Ok`
```json
{
"self": "ms-00",
"sharding": false,
"remotes": {
"ms-00": {
"url": "http://ms-1235.example.meilisearch.io",
"searchApiKey": "Ecd1SDDi4pqdJD6qYLxD3y7VZAEb4d9j6LJgt4d6xas",
"writeApiKey": "O2OaIHgwGuHNx9duH6kSe1YJ55Bh0dXvLhbr8FQVvr3vRVViBO"
},
"ms-01": {
"url": "http://ms-4242.example.meilisearch.io",
"searchApiKey": "hrVu-OMcjPGElK7692K7bwriBoGyHXTMvB5NmZkMKqQ",
"writeApiKey": "bd1ldDoFlfyeoFDe8f3GVNiE8AHX86chmFuzOW7nWYUbPa7ww3"
}
}
}
```
## Update the network object
Update the `self` and `remotes` fields of the network object.
Updates to the network object are **partial**. Only provide the fields you intend to update. Fields not present in the payload will remain unchanged.
To reset `self`, `sharding` and `remotes` to their original value, set them to `null`. To remove a single `remote` from your network, set the value of its name to `null`.
### Body
| Name | Type | Default value | Description |
| :-------------------------- | :------ | :------------ | :------------------------------------------------------------------- |
| **[`self`](#self)** | String | `null` | The name of the current instance |
| **[`sharding`](#sharding)** | Boolean | `false` | Whether sharding should be enabled on the network |
| **[`remotes`](#remotes)** | String | `null` | A list of remote objects describing accessible Meilisearch instances |
### Example
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/network' \
-H 'Content-Type: application/json' \
--data-binary '{
"self": "ms-00",
"remotes": {
"ms-00": {
"url": "http://INSTANCE_URL",
"searchApiKey": "INSTANCE_API_KEY"
},
"ms-01": {
"url": "http://ANOTHER_INSTANCE_URL",
"searchApiKey": "ANOTHER_INSTANCE_API_KEY"
}
}
}'
```
#### Response: `200 Ok`
```json
{
"self": "ms-00",
"sharding": true,
"remotes": {
"ms-00": {
"url": "http://INSTANCE_URL",
"searchApiKey": "INSTANCE_API_KEY",
"writeApiKey": "INSTANCE_WRITE_API_KEY"
},
"ms-01": {
"url": "http://ANOTHER_INSTANCE_URL",
"searchApiKey": "ANOTHER_INSTANCE_API_KEY",
"writeApiKey": "ANOTHER_INSTANCE_WRITE_API_KEY"
}
}
}
```
# Overview
Source: https://www.meilisearch.com/docs/reference/api/overview
Consult this page for an overview of how to query Meilisearch's API, which types of parameters it supports, and how it structures its responses.
export const RouteHighlighter = ({method, path}) =>
{method}
{path}
;
This reference describes the general behavior of Meilisearch's RESTful API.
If you are new to Meilisearch, check out the [getting started](/learn/self_hosted/getting_started_with_self_hosted_meilisearch).
## OpenAPI
Meilisearch OpenAPI specifications (`meilisearch-openapi.json`) are attached to the [latest release of Meilisearch](https://github.com/meilisearch/meilisearch/releases/tag/latest)
## Document conventions
This API documentation uses the following conventions:
* Curly braces (`{}`) in API routes represent path parameters, for example, GET `/indexes/{index_uid}`
* Required fields are marked by an asterisk (`*`)
* Placeholder text is in uppercase characters with underscore delimiters, for example, `MASTER_KEY`
## Authorization
By [providing Meilisearch with a master key at launch](/learn/security/basic_security), you protect your instance from unauthorized requests. The provided master key must be at least 16 bytes. From then on, you must include the `Authorization` header along with a valid API key to access protected routes (all routes except [`/health`](/reference/api/health).
```bash cURL
# replace the MASTER_KEY placeholder with your master key
curl \
-X GET 'MEILISEARCH_URL/keys' \
-H 'Authorization: Bearer MASTER_KEY'
```
```javascript JS
const client = new MeiliSearch({ host: 'http://localhost:7700', apiKey: 'masterKey' })
client.getKeys()
```
```python Python
client = Client('http://localhost:7700', 'masterKey')
client.get_keys()
```
```php PHP
$client = new Client('http://localhost:7700', 'masterKey');
$client->getKeys();
```
```java Java
Client client = new Client(new Config("http://localhost:7700", "masterKey"));
client.getKeys();
```
```ruby Ruby
client = MeiliSearch::Client.new('http://localhost:7700', 'masterKey')
client.keys
```
```go Go
client := meilisearch.New("http://localhost:7700", meilisearch.WithAPIKey("masterKey"))
client.GetKeys(nil);
```
```csharp C#
MeilisearchClient client = new MeilisearchClient("http://localhost:7700", "masterKey");
var keys = await client.GetKeysAsync();
```
```rust Rust
let client = Client::new("http://localhost:7700", Some("masterKey")); let keys = client .get_keys() .await .unwrap();
```
```swift Swift
client = try MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey")
client.getKeys { result in
switch result {
case .success(let keys):
print(keys)
case .failure(let error):
print(error)
}
}
```
```dart Dart
var client = MeiliSearchClient('http://localhost:7700', 'masterKey');
await client.getKeys();
```
The [`/keys`](/reference/api/keys) route can only be accessed using the master key. For security reasons, we recommend using regular API keys for all other routes.
v0.24 and below use the `X-MEILI-API-KEY: apiKey` authorization header:
```bash cURL
curl \
-X GET 'http:///version' \
-H 'X-Meili-API-Key: API_KEY'
```
[To learn more about keys and security, refer to our security tutorial.](/learn/security/basic_security)
## Pagination
Meilisearch paginates all GET routes that return multiple resources, for example, GET `/indexes`, GET `/documents`, GET `/keys`, etc. This allows you to work with manageable chunks of data. All these routes return 20 results per page, but you can configure it using the `limit` query parameter. You can move between pages using `offset`.
All paginated responses contain the following fields:
| Name | Type | Description |
| :----------- | :------ | :--------------------------- |
| **`offset`** | Integer | Number of resources skipped |
| **`limit`** | Integer | Number of resources returned |
| **`total`** | Integer | Total number of resources |
### `/tasks` endpoint
Since the `/tasks` endpoint uses a different type of pagination, the response contains different fields. You can read more about it in the [tasks API reference](/reference/api/tasks#get-tasks).
## Parameters
Parameters are options you can pass to an API endpoint to modify its response. There are three main types of parameters in Meilisearch's API: request body parameters, path parameters, and query parameters.
### Request body parameters
These parameters are mandatory parts of POST, PUT, and PATCH requests. They accept a wide variety of values and data types depending on the resource you're modifying. You must add these parameters to your request's data payload.
### Path parameters
These are parameters you pass to the API in the endpoint's path. They are used to identify a resource uniquely. You can have multiple path parameters, for example, `/indexes/{index_uid}/documents/{document_id}`.
If an endpoint does not take any path parameters, this section is not present in that endpoint's documentation.
### Query parameters
These optional parameters are a sequence of key-value pairs and appear after the question mark (`?`) in the endpoint. You can list multiple query parameters by separating them with an ampersand (`&`). The order of query parameters does not matter. They are mostly used with GET endpoints.
If an endpoint does not take any query parameters, this section is not present in that endpoint's documentation.
## Headers
### Content type
Any API request with a payload (`--data-binary`) requires a `Content-Type` header. Content type headers indicate the media type of the resource, helping the client process the response body correctly.
Meilisearch currently supports the following formats:
* `Content-Type: application/json` for JSON
* `Content-Type: application/x-ndjson` for NDJSON
* `Content-Type: text/csv` for CSV
Only the [add documents](/reference/api/documents#add-or-replace-documents) and [update documents](/reference/api/documents#add-or-update-documents) endpoints accept NDJSON and CSV. For all others, use `Content-Type: application/json`.
### Content encoding
The `Content-Encoding` header indicates the media type is compressed by a given algorithm. Compression improves transfer speed and reduces bandwidth consumption by sending and receiving smaller payloads. The `Accept-Encoding` header, instead, indicates the compression algorithm the client understands.
Meilisearch supports the following compression methods:
* `br`: uses the [Brotli](https://en.wikipedia.org/wiki/Brotli) algorithm
* `deflate`: uses the [zlib](https://en.wikipedia.org/wiki/Zlib) structure with the [deflate](https://en.wikipedia.org/wiki/DEFLATE) compression algorithm
* `gzip`: uses the [gzip](https://en.wikipedia.org/wiki/Gzip) algorithm
#### Request compression
The code sample below uses the `Content-Encoding: gzip` header, indicating that the request body is compressed using the `gzip` algorithm:
```
cat ~/movies.json | gzip | curl -X POST 'MEILISEARCH_URL/indexes/movies/documents' --data-binary @- -H 'Content-Type: application/json' -H 'Content-Encoding: gzip'
```
#### Response compression
Meilisearch compresses a response if the request contains the `Accept-Encoding` header. The code sample below uses the `gzip` algorithm:
```
curl -sH 'Accept-encoding: gzip' 'MEILISEARCH_URL/indexes/movies/search' | gzip -
```
## Request body
The request body is data sent to the API. It is used with PUT, POST, and PATCH methods to create or update a resource. You must provide request bodies in JSON.
## Response body
Meilisearch is an **asynchronous API**. This means that in response to most write requests, you will receive a summarized version of the `task` object:
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "indexUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
See more information about [asynchronous operations](/learn/async/asynchronous_operations).
## Data types
The Meilisearch API supports [JSON data types](https://www.w3schools.com/js/js_json_datatypes.asp).
# Search
Source: https://www.meilisearch.com/docs/reference/api/search
The /search route allows you to search your indexed documents. This route includes a large number of parameters you can use to customize returned search results.
export const NoticeTag = ({label}) =>
{label}
;
export const RouteHighlighter = ({method, path}) =>
{method}
{path}
;
Meilisearch exposes two routes to perform searches:
* A POST route: this is the preferred route when using API authentication, as it allows [preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) caching and better performance
* A GET route: the usage of this route is discouraged, unless you have good reason to do otherwise (specific caching abilities for example)
You may find exhaustive descriptions of the parameters accepted by the two routes [at the end of this article](#search-parameters).
## Search in an index with POST
Search for documents matching a specific query in the given index.
This is the preferred endpoint to perform search when an API key is required, as it allows for [preflight requests](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) to be cached. Caching preflight requests **considerably improves search speed**.
By default, [this endpoint returns a maximum of 1000 results](/learn/resources/known_limitations#maximum-number-of-results-per-search). If you want to scrape your database, use the [get documents endpoint](/reference/api/documents#get-documents-with-post) instead.
### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
### Body
| Search Parameter | Type | Default value | Description |
| :------------------------------------------------------------------------------ | :------------------------- | :------------ | :--------------------------------------------------------------------- |
| **[`q`](#query-q)** | String | `""` | Query string |
| **[`offset`](#offset)** | Integer | `0` | Number of documents to skip |
| **[`limit`](#limit)** | Integer | `20` | Maximum number of documents returned |
| **[`hitsPerPage`](#number-of-results-per-page)** | Integer | `1` | Maximum number of documents returned for a page |
| **[`page`](#page)** | Integer | `1` | Request a specific page of results |
| **[`filter`](#filter)** | String or array of strings | `null` | Filter queries by an attribute's value |
| **[`facets`](#facets)** | Array of strings | `null` | Display the count of matches per facet |
| **[`distinct`](#distinct-attributes-at-search-time)** | String | `null` | Restrict search to documents with unique values of specified attribute |
| **[`attributesToRetrieve`](#attributes-to-retrieve)** | Array of strings | `["*"]` | Attributes to display in the returned documents |
| **[`attributesToCrop`](#attributes-to-crop)** | Array of strings | `null` | Attributes whose values have to be cropped |
| **[`cropLength`](#crop-length)** | Integer | `10` | Maximum length of cropped value in words |
| **[`cropMarker`](#crop-marker)** | String | `"…"` | String marking crop boundaries |
| **[`attributesToHighlight`](#attributes-to-highlight)** | Array of strings | `null` | Highlight matching terms contained in an attribute |
| **[`highlightPreTag`](#highlight-tags)** | String | `""` | String inserted at the start of a highlighted term |
| **[`highlightPostTag`](#highlight-tags)** | String | `""` | String inserted at the end of a highlighted term |
| **[`showMatchesPosition`](#show-matches-position)** | Boolean | `false` | Return matching terms location |
| **[`sort`](#sort)** | Array of strings | `null` | Sort search results by an attribute's value |
| **[`matchingStrategy`](#matching-strategy)** | String | `last` | Strategy used to match query terms within documents |
| **[`showRankingScore`](#ranking-score)** | Boolean | `false` | Display the global ranking score of a document |
| **[`showRankingScoreDetails`](#ranking-score-details)** | Boolean | `false` | Adds a detailed global ranking score field |
| **[`rankingScoreThreshold`](#ranking-score-threshold)** | Number | `null` | Excludes results with low ranking scores |
| **[`attributesToSearchOn`](#customize-attributes-to-search-on-at-search-time)** | Array of strings | `["*"]` | Restrict search to the specified attributes |
| **[`hybrid`](#hybrid-search)** | Object | `null` | Return results based on query keywords and meaning |
| **[`vector`](#vector)** | Array of numbers | `null` | Search using a custom query vector |
| **[`retrieveVectors`](#display-_vectors-in-response)** | Boolean | `false` | Return document and query vector data |
| **[`locales`](#query-locales)** | Array of strings | `null` | Explicitly specify languages used in a query |
| **[`media`](#media)** | Object | `null` | Perform AI-powered search queries with multimodal content |
### Response
| Name | Type | Description |
| :----------------------- | :--------------- | :---------------------------------------------------------- |
| **`hits`** | Array of objects | Results of the query |
| **`offset`** | Number | Number of documents skipped |
| **`limit`** | Number | Number of documents to take |
| **`estimatedTotalHits`** | Number | Estimated total number of matches |
| **`totalHits`** | Number | Exhaustive total number of matches |
| **`totalPages`** | Number | Exhaustive total number of search result pages |
| **`hitsPerPage`** | Number | Number of results on each page |
| **`page`** | Number | Current search results page |
| **`facetDistribution`** | Object | **[Distribution of the given facets](#facetdistribution)** |
| **`facetStats`** | Object | [The numeric `min` and `max` values per facet](#facetstats) |
| **`processingTimeMs`** | Number | Processing time of the query |
| **`query`** | String | Query originating the response |
#### Exhaustive and estimated total number of search results
By default, Meilisearch only returns an estimate of the total number of search results in a query: `estimatedTotalHits`. This happens because Meilisearch prioritizes relevancy and performance over providing an exhaustive number of search results. When working with `estimatedTotalHits`, use `offset` and `limit` to navigate between search results.
If you require the total number of search results, use the `hitsPerPage` and `page` search parameters in your query. The response to this query replaces `estimatedTotalHits` with `totalHits` and includes an extra field with number of search results pages based on your `hitsPerPage`: `totalPages`. Using `totalHits` and `totalPages` may result in slightly reduced performance, but is recommended when creating UI elements such as numbered page selectors.
Neither `estimatedTotalHits` nor `totalHits` can exceed the limit configured in [the `maxTotalHits` index setting](/reference/api/settings#pagination).
You can [read more about pagination in our dedicated guide](/guides/front_end/pagination).
### Example
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{ "q": "american ninja" }'
```
```javascript JS
client.index('movies').search('American ninja')
```
```python Python
client.index('movies').search('American ninja')
```
```php PHP
$client->index('movies')->search('american ninja');
```
```java Java
client.index("movies").search("American ninja");
```
```ruby Ruby
client.index('movies').search('american ninja')
```
```go Go
client.Index("movies").Search("american ninja", &meilisearch.SearchRequest{})
```
```csharp C#
await client.Index("movies").SearchAsync("American ninja");
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("american ninja")
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(query: "American ninja")
client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').search('American ninja');
```
#### Response: `200 Ok`
```json
{
"hits": [
{
"id": 2770,
"title": "American Pie 2",
"poster": "https://image.tmdb.org/t/p/w1280/q4LNgUnRfltxzp3gf1MAGiK5LhV.jpg",
"overview": "The whole gang are back and as close as ever. They decide to get even closer by spending the summer together at a beach house. They decide to hold the biggest…",
"release_date": 997405200
},
{
"id": 190859,
"title": "American Sniper",
"poster": "https://image.tmdb.org/t/p/w1280/svPHnYE7N5NAGO49dBmRhq0vDQ3.jpg",
"overview": "U.S. Navy SEAL Chris Kyle takes his sole mission—protect his comrades—to heart and becomes one of the most lethal snipers in American history. His pinpoint accuracy not only saves countless lives but also makes him a prime…",
"release_date": 1418256000
},
…
],
"offset": 0,
"limit": 20,
"estimatedTotalHits": 976,
"processingTimeMs": 35,
"query": "american "
}
```
## Search in an index with GET
Search for documents matching a specific query in the given index.
This endpoint only accepts [string filter expressions](/learn/filtering_and_sorting/filter_expression_reference).
This endpoint should only be used when no API key is required. If an API key is required, use the [POST](/reference/api/search#search-in-an-index-with-post) route instead.
By default, [this endpoint returns a maximum of 1000 results](/learn/resources/known_limitations#maximum-number-of-results-per-search). If you want to scrape your database, use the [get documents endpoint](/reference/api/documents#get-documents-with-post) instead.
### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
### Query parameters
| Search Parameter | Type | Default value | Description |
| :------------------------------------------------------------------------------ | :------------------------- | :------------ | :--------------------------------------------------------------------- |
| **[`q`](#query-q)** | String | `""` | Query string |
| **[`offset`](#offset)** | Integer | `0` | Number of documents to skip |
| **[`limit`](#limit)** | Integer | `20` | Maximum number of documents returned |
| **[`hitsPerPage`](#number-of-results-per-page)** | Integer | `1` | Maximum number of documents returned for a page |
| **[`page`](#page)** | Integer | `1` | Request a specific page of results |
| **[`filter`](#filter)** | String or array of strings | `null` | Filter queries by an attribute's value |
| **[`facets`](#facets)** | Array of strings | `null` | Display the count of matches per facet |
| **[`distinct`](#distinct-attributes-at-search-time)** | String | `null` | Restrict search to documents with unique values of specified attribute |
| **[`attributesToRetrieve`](#attributes-to-retrieve)** | Array of strings | `["*"]` | Attributes to display in the returned documents |
| **[`attributesToCrop`](#attributes-to-crop)** | Array of strings | `null` | Attributes whose values have to be cropped |
| **[`cropLength`](#crop-length)** | Integer | `10` | Maximum length of cropped value in words |
| **[`cropMarker`](#crop-marker)** | String | `"…"` | String marking crop boundaries |
| **[`attributesToHighlight`](#attributes-to-highlight)** | Array of strings | `null` | Highlight matching terms contained in an attribute |
| **[`highlightPreTag`](#highlight-tags)** | String | `""` | String inserted at the start of a highlighted term |
| **[`highlightPostTag`](#highlight-tags)** | String | `""` | String inserted at the end of a highlighted term |
| **[`showMatchesPosition`](#show-matches-position)** | Boolean | `false` | Return matching terms location |
| **[`sort`](#sort)** | Array of strings | `null` | Sort search results by an attribute's value |
| **[`matchingStrategy`](#matching-strategy)** | String | `last` | Strategy used to match query terms within documents |
| **[`showRankingScore`](#ranking-score)** | Boolean | `false` | Display the global ranking score of a document |
| **[`showRankingScoreDetails`](#ranking-score-details)** | Boolean | `false` | Adds a detailed global ranking score field |
| **[`rankingScoreThreshold`](#ranking-score-threshold)** | Number | `null` | Excludes results with low ranking scores |
| **[`attributesToSearchOn`](#customize-attributes-to-search-on-at-search-time)** | Array of strings | `["*"]` | Restrict search to the specified attributes |
| **[`hybrid`](#hybrid-search)** | Object | `null` | Return results based on query keywords and meaning |
| **[`vector`](#vector)** | Array of numbers | `null` | Search using a custom query vector |
| **[`retrieveVectors`](#display-_vectors-in-response)** | Boolean | `false` | Return document and query vector data |
| **[`locales`](#query-locales)** | Array of strings | `null` | Explicitly specify languages used in a query |
### Response
| Name | Type | Description |
| :----------------------- | :--------------- | :---------------------------------------------------------- |
| **`hits`** | Array of objects | Results of the query |
| **`offset`** | Number | Number of documents skipped |
| **`limit`** | Number | Number of documents to take |
| **`estimatedTotalHits`** | Number | Estimated total number of matches |
| **`totalHits`** | Number | Exhaustive total number of matches |
| **`totalPages`** | Number | Exhaustive total number of search result pages |
| **`hitsPerPage`** | Number | Number of results on each page |
| **`page`** | Number | Current search results page |
| **`facetDistribution`** | Object | **[Distribution of the given facets](#facetdistribution)** |
| **`facetStats`** | Object | [The numeric `min` and `max` values per facet](#facetstats) |
| **`processingTimeMs`** | Number | Processing time of the query |
| **`query`** | String | Query originating the response |
### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/movies/search?q=american%20ninja'
```
```javascript JS
client.index('movies').searchGet('American ninja')
```
```dart Dart
await client.index('movies').search('American ninja');
```
#### Response: `200 Ok`
```json
{
"hits": [
{
"id": 2770,
"title": "American Pie 2",
"poster": "https://image.tmdb.org/t/p/w1280/q4LNgUnRfltxzp3gf1MAGiK5LhV.jpg",
"overview": "The whole gang are back and as close as ever. They decide to get even closer by spending the summer together at a beach house. They decide to hold the biggest…",
"release_date": 997405200
},
{
"id": 190859,
"title": "American Sniper",
"poster": "https://image.tmdb.org/t/p/w1280/svPHnYE7N5NAGO49dBmRhq0vDQ3.jpg",
"overview": "U.S. Navy SEAL Chris Kyle takes his sole mission—protect his comrades—to heart and becomes one of the most lethal snipers in American history. His pinpoint accuracy not only saves countless lives but also makes him a prime…",
"release_date": 1418256000
},
…
],
"offset": 0,
"limit": 20,
"estimatedTotalHits": 976,
"processingTimeMs": 35,
"query": "american "
}
```
## Search parameters
Here follows an exhaustive description of each search parameter currently available when using the search endpoint. Unless otherwise noted, all parameters are valid for the `GET /indexes/{index_uid}/search`, `POST /indexes/{index_uid}/search`, and `/multi-search` routes.
If [using the `GET` route to perform a search](/reference/api/search#search-in-an-index-with-get), all parameters must be **URL-encoded**.
This is not necessary when using the `POST` route or one of our [SDKs](/learn/resources/sdks).
### Query (q)
**Parameter**: `q`
**Expected value**: Any string
**Default value**: `null`
Sets the search terms.
Meilisearch only considers the first ten words of any given search query. This is necessary in order to deliver a [fast search-as-you-type experience](/learn/resources/known_limitations#maximum-number-of-query-words).
#### Example
You can search for films mentioning `shifu` by setting the `q` parameter:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{ "q": "shifu" }'
```
```javascript JS
client.index('movies').search('shifu')
```
```python Python
client.index('movies').search('shifu')
```
```php PHP
$client->index('movies')->search('shifu');
```
```java Java
client.index("movies").search("shifu");
```
```ruby Ruby
client.index('movies').search('shifu')
```
```go Go
resp, err := client.Index("movies").Search("shifu", &meilisearch.SearchRequest{})
```
```csharp C#
await client.Index("movies").SearchAsync("shifu");
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("shifu")
.execute()
.await
.unwrap();
```
```swift Swift
client.index("movies").search(SearchParameters(query: "shifu")) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').search('shifu');
```
This will give you a list of documents that contain your query terms in at least one attribute.
```json
{
"hits": [
{
"id": 50393,
"title": "Kung Fu Panda Holiday",
"poster": "https://image.tmdb.org/t/p/w500/rV77WxY35LuYLOuQvBeD1nyWMuI.jpg",
"overview": "The Winter Feast is Po's favorite holiday. Every year he and his father hang decorations, cook together, and serve noodle soup to the villagers. But this year Shifu informs Po that as Dragon Warrior, it is his duty to host the formal Winter Feast at the Jade Palace.",
"release_date": 1290729600,
"genres": [
"Animation",
"Family",
"TV Movie"
]
}
],
"query": "shifu"
}
```
#### Query term normalization
Query terms go through a normalization process that removes [non-spacing marks](https://www.compart.com/en/unicode/category/Mn). Because of this, Meilisearch effectively ignores accents and diacritics when returning results. For example, searching for `"sábia"` returns documents containing `"sábia"`, `"sabiá"`, and `"sabia"`.
Normalization also converts all letters to lowercase. Searching for `"Video"` returns the same results as searching for `"video"`, `"VIDEO"`, or `"viDEO"`.
#### Placeholder search
When `q` isn't specified, Meilisearch performs a **placeholder search**. A placeholder search returns all searchable documents in an index, modified by any search parameters used and sorted by that index's [custom ranking rules](/learn/relevancy/custom_ranking_rules). Since there is no query term, the [built-in ranking rules](/learn/relevancy/ranking_rules) **do not apply.**
If the index has no sort or custom ranking rules, the results are returned in the order of their internal database position.
Placeholder search is particularly useful when building a [faceted search interfaces](/learn/filtering_and_sorting/search_with_facet_filters), as it allows users to view the catalog and alter sorting rules without entering a query.
#### Phrase search
If you enclose search terms in double quotes (`"`), Meilisearch will only return documents containing those terms in the order they were given. This is called a **phrase search**.
Phrase searches are case-insensitive and ignore [soft separators such as `-`, `,`, and `:`](/learn/engine/datatypes). Using a hard separator within a phrase search effectively splits it into multiple separate phrase searches: `"Octavia.Butler"` will return the same results as `"Octavia" "Butler"`.
You can combine phrase search and normal queries in a single search request. In this case, Meilisearch will first fetch all documents with exact matches to the given phrase(s), and [then proceed with its default behavior](/learn/relevancy/relevancy).
##### Example
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{ "q": "\"african american\" horror" }'
```
```javascript JS
client.index('movies')
.search('"african american" horror')
```
```python Python
client.index('movies').search('"african american" horror')
```
```php PHP
$client->index('movies')->search('"african american" horror');
```
```java Java
client.index("movies").search("\"african american\" horror");
```
```ruby Ruby
client.index('movies').search('"african american" horror')
```
```go Go
resp, err := client.Index("movies").Search("\"african american\" horror", &meilisearch.SearchRequest{})
```
```csharp C#
await client.Index("movies").SearchAsync("\"african american\" horror");
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("\"african american\" horror")
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "\"african american\" horror")
client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').search('"african american" horror');
```
#### Negative search
Use the minus (`-`) operator in front of a word or phrase to exclude it from search results.
##### Example
The following query returns all documents that do not include the word "escape":
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{ "q": "-escape" }'
```
```javascript JS
client.index('movies').search('-escape')
```
```php PHP
$client->index('movies')->search('-escape');
```
```rust Rust
let results = index.search()
.with_query("-escape")
.execute()
.await
.unwrap();
```
Negative search can be used together with phrase search:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{ "q": "-\"escape room\"" }'
```
```javascript JS
client.index('movies').search('-"escape"')
```
```php PHP
$client->index('movies')->search('-"escape"');
```
```rust Rust
let results = index.search()
.with_query("-\"escape room\"")
.execute()
.await
.unwrap();
```
### Offset
**Parameter**: `offset`
**Expected value**: Any positive integer
**Default value**: `0`
Sets the starting point in the search results, effectively skipping over a given number of documents.
Queries using `offset` and `limit` only return an estimate of the total number of search results.
You can [paginate search results](/guides/front_end/pagination) by making queries combining both `offset` and `limit`.
Setting `offset` to a value greater than an [index's `maxTotalHits`](/reference/api/settings#update-pagination-settings) returns an empty array.
#### Example
If you want to skip the **first** result in a query, set `offset` to `1`:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "shifu",
"offset": 1
}'
```
```javascript JS
client.index('movies').search('shifu', {
offset: 1
})
```
```python Python
client.index('movies').search('shifu', {
'offset': 1
})
```
```php PHP
$client->index('movies')->search('shifu', ['offset' => 1]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("shifu").offset(1).build();
client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('shifu', {
offset: 1
})
```
```go Go
resp, err := client.Index("movies").Search("shifu", &meilisearch.SearchRequest{
Offset: 1,
})
```
```csharp C#
var sq = new SearchQuery
{
Offset = 1
};
var result = await client.Index("movies").SearchAsync("shifu", sq);
if(result is SearchResult pagedResults)
{
}
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("shifu")
.with_offset(1)
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "shifu",
offset: 1)
client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').search('shifu', SearchQuery(offset: 1));
```
### Limit
**Parameter**: `limit`
**Expected value**: Any positive integer or zero
**Default value**: `20`
Sets the maximum number of documents returned by a single query.
You can [paginate search results](/guides/front_end/pagination) by making queries combining both `offset` and `limit`.
A search query cannot return more results than configured in [`maxTotalHits`](/reference/api/settings#pagination-object), even if the value of `limit` is greater than the value of `maxTotalHits`.
#### Example
If you want your query to return only **two** documents, set `limit` to `2`:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "shifu",
"limit": 2
}'
```
```javascript JS
client.index('movies').search('shifu', {
limit: 2
})
```
```python Python
client.index('movies').search('shifu', {
'limit': 2
})
```
```php PHP
$client->index('movies')->search('shifu', ['limit' => 2]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("shifu").limit(2).build();
client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('shifu', {
limit: 2
})
```
```go Go
resp, err := client.Index("movies").Search("shifu", &meilisearch.SearchRequest{
Limit: 2,
})
```
```csharp C#
var sq = new SearchQuery
{
Limit = 2
};
var result = await client.Index("movies").SearchAsync("shifu", sq);
if(result is SearchResult pagedResults)
{
}
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("shifu")
.with_limit(2)
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "shifu",
limit: 2)
client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').search('shifu', SearchQuery(limit: 2));
```
### Number of results per page
**Parameter**: `hitsPerPage`
**Expected value**: Any positive integer
**Default value**: `20`
Sets the maximum number of documents returned for a single query. The value configured with this parameter dictates the number of total pages: if Meilisearch finds a total of `20` matches for a query and your `hitsPerPage` is set to `5`, `totalPages` is `4`.
Queries containing `hitsPerPage` are exhaustive and do not return an `estimatedTotalHits`. Instead, the response body will include `totalHits` and `totalPages`.
If you set `hitsPerPage` to `0`, Meilisearch processes your request, but does not return any documents. In this case, the response body will include the exhaustive value for `totalHits`. The response body will also include `totalPages`, but its value will be `0`.
You can use `hitsPerPage` and `page` to [paginate search results](/guides/front_end/pagination).
`hitsPerPage` and `page` take precedence over `offset` and `limit`. If a query contains either `hitsPerPage` or `page`, any values passed to `offset` and `limit` are ignored.
`hitsPerPage` and `page` are resource-intensive options and might negatively impact search performance. This is particularly likely if [`maxTotalHits`](/reference/api/settings#pagination) is set to a value higher than its default.
#### Example
The following example returns the first 15 results for a query:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "",
"hitsPerPage": 15
}'
```
```javascript JS
client.index('movies').search('', {
hitsPerPage: 15
})
```
```python Python
client.index('movies').search('', {'hitsPerPage': 15})
```
```php PHP
$client->index('movies')->search('', ['hitsPerPage' => 15]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("").hitsPerPage(15).build();
SearchResultPaginated searchResult = client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('', hits_per_page: 15)
```
```go Go
client.Index("movies").Search("", &meilisearch.SearchRequest{
HitsPerPage: 15,
})
```
```csharp C#
var result = await client.Index("movies").SearchAsync("", new SearchQuery { HitsPerPage = 15 });
if(result is PaginatedSearchResult pagedResults)
{
}
```
```rust Rust
client.index("movies").search().with_hits_per_page(15).execute().await?;
```
```swift Swift
let searchParameters = SearchParameters(query: "", hitsPerPage: 15)
client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client
.index('movies')
.search('', SearchQuery(hitsPerPage: 15))
.asPaginatedResult();
```
### Page
**Parameter**: `page`
**Expected value**: Any positive integer
**Default value**: `1`
Requests a specific results page. Pages are calculated using the `hitsPerPage` search parameter.
Queries containing `page` are exhaustive and do not return an `estimatedTotalHits`. Instead, the response body will include two new fields: `totalHits` and `totalPages`.
If you set `page` to `0`, Meilisearch processes your request, but does not return any documents. In this case, the response body will include the exhaustive values for `facetDistribution`, `totalPages`, and `totalHits`.
You can use `hitsPerPage` and `page` to [paginate search results](/guides/front_end/pagination).
`hitsPerPage` and `page` take precedence over `offset` and `limit`. If a query contains either `hitsPerPage` or `page`, any values passed to `offset` and `limit` are ignored.
`hitsPerPage` and `page` are resource-intensive options and might negatively impact search performance. This is particularly likely if [`maxTotalHits`](/reference/api/settings#pagination) is set to a value higher than its default.
#### Example
The following example returns the second page of search results:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "",
"page": 2
}'
```
```javascript JS
client.index('movies').search('', {
page: 2
})
```
```python Python
client.index('movies').search('', {'page': 2})
```
```php PHP
$client->index('movies')->search('', ['page' => 2]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("").page(15).build();
SearchResultPaginated searchResult = client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('', page: 2)
```
```go Go
client.Index("movies").Search("", &meilisearch.SearchRequest{
Page: 2,
})
```
```csharp C#
var result = await client.Index("movies").SearchAsync("", new SearchQuery { Page = 2 });
if(result is PaginatedSearchResult pagedResults)
{
}
```
```rust Rust
client.index("movies").search().with_page(2).execute().await?;
```
```swift Swift
let searchParameters = SearchParameters(query: "", page: 15)
client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client
.index('movies')
.search('', SearchQuery(page: 2))
.asPaginatedResult();
```
### Filter
**Parameter**: `filter`
**Expected value**: A filter expression written as a string or an array of strings
**Default value**: `[]`
Uses filter expressions to refine search results. Attributes used as filter criteria must be added to the [`filterableAttributes` list](/reference/api/settings#filterable-attributes).
For more information, [read our guide on how to use filters and build filter expressions](/learn/filtering_and_sorting/filter_search_results).
#### Example
You can write a filter expression in string syntax using logical connectives:
```
"(genres = horror OR genres = mystery) AND director = 'Jordan Peele'"
```
You can write the same filter as an array:
```
[["genres = horror", "genres = mystery"], "director = 'Jordan Peele'"]
```
You can then use the filter in a search query:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "thriller",
"filter": [
[
"genres = Horror",
"genres = Mystery"
],
"director = \"Jordan Peele\""
]
}'
```
```javascript JS
client.index('movies')
.search('thriller', {
filter: [['genres = Horror', 'genres = Mystery'], 'director = "Jordan Peele"']
})
```
```python Python
client.index('movies').search('thriller', {
'filter': [['genres = Horror', 'genres = Mystery'], 'director = "Jordan Peele"']
})
```
```php PHP
$client->index('movies')->search('thriller', [
'filter' => [['genres = Horror', 'genres = Mystery'], 'director = "Jordan Peele"']
]);
```
```java Java
SearchRequest searchRequest =
SearchRequest.builder().q("thriller").filterArray(new String[][] {
new String[] {"genres = Horror", "genres = Mystery"},
new String[] {"director = \"Jordan Peele\""}}).build();
client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('thriller', {
filter: [['genres = Horror', 'genres = Mystery'], 'director = "Jordan Peele"']
})
```
```go Go
resp, err := client.Index("movies").Search("thriller", &meilisearch.SearchRequest{
Filter: [][]string{
[]string{"genres = Horror", "genres = Mystery"},
[]string{"director = \"Jordan Peele\""},
},
})
```
```csharp C#
var sq = new SearchQuery
{
Filter = "(genre = 'Horror' AND genre = 'Mystery') OR director = 'Jordan Peele'"
};
await client.Index("movies").SearchAsync("thriller", sq);
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("thriller")
.with_filter("(genres = Horror AND genres = Mystery) OR director = \"Jordan Peele\"")
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "thriller",
filter: [
[
"genres = Horror",
"genres = Mystery"
],
"director = \"Jordan Peele\""
])
client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').search(
'thriller',
SearchQuery(filter: [
['genres = Horror', 'genres = Mystery'],
'director = "Jordan Peele"'
]));
```
#### Filtering results with `_geoRadius` and `_geoBoundingBox`
If your documents contain `_geo` data, you can use the `_geoRadius` and `_geoBoundingBox` built-in filter rules to filter results according to their geographic position.
`_geoRadius` establishes a circular area based on a central point and a radius. This filter rule requires three parameters: `lat`, `lng` and `distance_in_meters`.
```json
_geoRadius(lat, lng, distance_in_meters)
```
`lat` and `lng` should be geographic coordinates expressed as floating point numbers. `distance_in_meters` indicates the radius of the area within which you want your results and should be an integer.
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/restaurants/search' \
-H 'Content-type:application/json' \
--data-binary '{ "filter": "_geoRadius(45.472735, 9.184019, 2000)" }'
```
```javascript JS
client.index('restaurants').search('', {
filter: ['_geoRadius(45.472735, 9.184019, 2000)'],
})
```
```python Python
client.index('restaurants').search('', {
'filter': '_geoRadius(45.472735, 9.184019, 2000)'
})
```
```php PHP
$client->index('restaurants')->search('', [
'filter' => '_geoRadius(45.472735, 9.184019, 2000)'
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("").filter(new String[] {"_geoRadius(45.472735, 9.184019, 2000)"}).build();
client.index("restaurants").search(searchRequest);
```
```ruby Ruby
client.index('restaurants').search('', { filter: '_geoRadius(45.472735, 9.184019, 2000)' })
```
```go Go
resp, err := client.Index("restaurants").Search("", &meilisearch.SearchRequest{
Filter: "_geoRadius(45.472735, 9.184019, 2000)",
})
```
```csharp C#
SearchQuery filters = new SearchQuery() { Filter = "_geoRadius(45.472735, 9.184019, 2000)" };
var restaurants = await client.Index("restaurants").SearchAsync("", filters);
```
```rust Rust
let results: SearchResults = client
.index("restaurants")
.search()
.with_filter("_geoRadius(45.472735, 9.184019, 2000)")
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
filter: "_geoRadius(45.472735, 9.184019, 2000)"
)
client.index("restaurants").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('restaurants').search(
'',
SearchQuery(
filterExpression: Meili.geoRadius(
(lat: 45.472735, lng: 9.184019),
2000,
),
),
);
```
`_geoBoundingBox` establishes a rectangular area based on the coordinates for its top right and bottom left corners. This filter rule requires two arrays of geographic coordinates:
```
_geoBoundingBox([{lat}, {lng}], [{lat}, {lng}])
```
`lat` and `lng` should be geographic coordinates expressed as floating point numbers. The first array indicates the top right corner and the second array indicates the bottom left corner of the bounding box.
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/restaurants/search' \
-H 'Content-type:application/json' \
--data-binary '{ "filter": "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])" }'
```
```javascript JS
client.index('restaurants').search('', {
filter: ['_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])'],
})
```
```python Python
client.index('restaurants').search('Batman', {
'filter': '_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])'
})
```
```php PHP
$client->index('restaurants')->search('', [
'filter' => '_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])'
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q()("").filter(new String[] {
"_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])"
}).build();
client.index("restaurants").search(searchRequest);
```
```ruby Ruby
client.index('restaurants').search('', { filter: ['_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])'] })
```
```go Go
client.Index("restaurants").Search("", &meilisearch.SearchRequest{
Filter: "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])",
})
```
```csharp C#
SearchQuery filters = new SearchQuery()
{
Filter = "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])"
};
var restaurants = await client.Index("restaurants").SearchAsync("restaurants", filters);
```
```rust Rust
let results: SearchResults = client
.index("restaurants")
.search()
.with_filter("_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])")
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
filter: "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])"
)
client.index("restaurants").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('restaurants').search(
'',
SearchQuery(
filter:
'_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])',
),
);
```
Meilisearch will throw an error if the top right corner is under the bottom left corner.
If any parameters are invalid or missing, Meilisearch returns an [`invalid_search_filter`](/reference/errors/error_codes#invalid_search_filter) error.
### Facets
**Parameter**: `facets`
**Expected value**: An array of `attribute`s or `["*"]`
**Default value**: `null`
Returns the number of documents matching the current search query for each given facet. This parameter can take two values:
* An array of attributes: `facets=["attributeA", "attributeB", …]`
* An asterisk—this will return a count for all facets present in `filterableAttributes`
By default, `facets` returns a maximum of 100 facet values for each faceted field. You can change this value using the `maxValuesPerFacet` property of the [`faceting` index settings](/reference/api/settings#faceting).
When `facets` is set, the search results object includes the [`facetDistribution`](#facetdistribution) and [`facetStats`](#facetstats) fields.
If an attribute used on `facets` has not been added to the `filterableAttributes` list, it will be ignored.
#### `facetDistribution`
`facetDistribution` contains the number of matching documents distributed among the values of a given facet. Each facet is represented as an object:
```json
{
…
"facetDistribution": {
"FACET_A": {
"FACET_VALUE_X": 6,
"FACET_VALUE_Y": 1,
},
"FACET_B": {
"FACET_VALUE_Z": 3,
"FACET_VALUE_W": 9,
},
},
…
}
```
`facetDistribution` contains an object for every attribute passed to the `facets` parameter. Each object contains the returned values for that attribute and the count of matching documents with that value. Meilisearch does not return empty facets.
#### `facetStats`
`facetStats` contains the lowest (`min`) and highest (`max`) numerical values across all documents in each facet. Only numeric values are considered:
```json
{
…
"facetStats": {
"rating": {
"min": 2.5,
"max": 4.7
}
}
…
}
```
If none of the matching documents have a numeric value for a facet, that facet is not included in the `facetStats` object. `facetStats` ignores string values, even if the string contains a number.
#### Example
Given a movie ratings database, the following code sample returns the number of `Batman` movies per genre along with the minimum and maximum ratings:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movie_ratings/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "Batman",
"facets": ["genres", "rating"]
}'
```
```javascript JS
client.index('movie_ratings').search('Batman', { facets: ['genres', 'rating'] })
```
```python Python
client.index('movie_ratings').search('Batman', {
'facets': ['genres', 'rating']
})
```
```php PHP
$client->index('movie_ratings')->search('Batman', [
'facets' => ['genres', 'rating']
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("Batman").facets(new String[]
{
"genres",
"rating"
}).build();
client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movie_ratings').search('Batman', {
facets: ['genres', 'rating']
})
```
```go Go
client.Index("movie_ratings").Search("Batman", &meilisearch.SearchRequest{
Facets: []string{
"genres",
"rating",
},
})
```
```csharp C#
var sq = new SearchQuery
{
Facets = new string[] { "genres", "rating" }
};
await client.Index("movie_ratings").SearchAsync("Batman", sq);
```
```rust Rust
let books = client.index("movie_ratings");
let results: SearchResults = SearchQuery::new(&books)
.with_query("Batman")
.with_facets(Selectors::Some(&["genres", "rating"))
.execute()
.await
.unwrap();
```
```dart Dart
await client
.index('movie_ratings')
.search('Batman', SearchQuery(facets: ['genres', 'rating']));
```
The response shows the facet distribution for `genres` and `rating`. Since `rating` is a numeric field, you get its minimum and maximum values in `facetStats`.
```json
{
…
"estimatedTotalHits": 22,
"query": "Batman",
"facetDistribution": {
"genres": {
"Action": 20,
"Adventure": 7,
…
"Thriller": 3
},
"rating": {
"2": 1,
…
"9.8": 1
}
},
"facetStats": {
"rating": {
"min": 2.0,
"max": 9.8
}
}
}
```
[Learn more about facet distribution in the faceted search guide.](/learn/filtering_and_sorting/search_with_facet_filters)
### Distinct attributes at search time
**Parameter**: `distinct`
**Expected value**: An `attribute` present in the `filterableAttributes` list
**Default value**: `null`
Defines one attribute in the `filterableAttributes` list as a distinct attribute. Distinct attributes indicate documents sharing the same value for the specified field are equivalent and only the most relevant one should be returned in search results.
This behavior is similar to the [`distinctAttribute` index setting](/reference/api/settings#distinct-attribute), but can be configured at search time. `distinctAttribute` acts as a default distinct attribute value you may override with `distinct`.
#### Examples
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/INDEX_NAME/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "QUERY TERMS",
"distinct": "ATTRIBUTE_A"
}'
```
```javascript JS
client.index('INDEX_NAME').search('QUERY TERMS', { distinct: 'ATTRIBUTE_A' })
```
```python Python
client.index('INDEX_NAME').search('QUERY_TERMS', { distinct: 'ATTRIBUTE_A' })
```
```php PHP
$client->index('INDEX_NAME')->search('QUERY TERMS', [
'distinct' => 'ATTRIBUTE_A'
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("QUERY TERMS").distinct("ATTRIBUTE_A").build();
client.index("INDEX_NAME").search(searchRequest);
```
```ruby Ruby
client.index('INDEX_NAME').search('QUERY TERMS', {
distinct: 'ATTRIBUTE_A'
})
```
```go Go
client.Index("INDEX_NAME").Search("QUERY TERMS", &meilisearch.SearchRequest{
Distinct: "ATTRIBUTE_A",
})
```
```csharp C#
var params = new SearchQuery()
{
Distinct = "ATTRIBUTE_A"
};
await client.Index("INDEX_NAME").SearchAsync("QUERY TERMS", params);
```
```rust Rust
let res = client
.index("INDEX_NAME")
.search()
.with_query("QUERY TERMS")
.with_distinct("ATTRIBUTE_A")
.execute()
.await
.unwrap();
```
### Attributes to retrieve
**Parameter**: `attributesToRetrieve`
**Expected value**: An array of `attribute`s or `["*"]`
**Default value**: `["*"]`
Configures which attributes will be retrieved in the returned documents.
If no value is specified, `attributesToRetrieve` uses the [`displayedAttributes` list](/reference/api/settings#displayed-attributes), which by default contains all attributes found in the documents.
If an attribute has been removed from `displayedAttributes`, `attributesToRetrieve` will silently ignore it and the field will not appear in your returned documents.
#### Example
To get only the `overview` and `title` fields, set `attributesToRetrieve` to `["overview", "title"]`.
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "shifu",
"attributesToRetrieve": [
"overview",
"title"
]
}'
```
```javascript JS
client.index('movies').search('shifu', {
attributesToRetrieve: ['overview', 'title']
})
```
```python Python
client.index('movies').search('shifu', {
'attributesToRetrieve': ['overview', 'title']
})
```
```php PHP
$client->index('movies')->search('shifu', [
'attributesToRetrieve' => ['overview', 'title']
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("a").attributesToRetrieve(new String[] {"overview", "title"}).build();
client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('shifu', {
attributes_to_retrieve: ['overview', 'title']
})
```
```go Go
resp, err := client.Index("movies").Search("shifu", &meilisearch.SearchRequest{
AttributesToRetrieve: []string{"overview", "title"},
})
```
```csharp C#
var sq = new SearchQuery
{
AttributesToRetrieve = new[] {"overview", "title"}
};
await client.Index("movies").SearchAsync("shifu", sq);
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("shifu")
.with_attributes_to_retrieve(Selectors::Some(&["overview", "title"]))
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "shifu",
attributesToRetrieve: ["overview", "title"])
client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').search(
'shifu', SearchQuery(attributesToRetrieve: ['overview', 'title']));
```
### Attributes to crop
**Parameter**: `attributesToCrop`
**Expected value**: An array of attributes or `["*"]`
**Default value**: `null`
Crops the selected fields in the returned results to the length indicated by the [`cropLength`](#crop-length) parameter. When `attributesToCrop` is set, each returned document contains an extra field called `_formatted`. This object contains the cropped version of the selected attributes.
By default, crop boundaries are marked by the ellipsis character (`…`). You can change this by using the [`cropMarker`](#crop-marker) search parameter.
Optionally, you can indicate a custom crop length for any attributes given to `attributesToCrop`: `attributesToCrop=["attributeNameA:5", "attributeNameB:9"]`. If configured, these values have priority over `cropLength`.
Instead of supplying individual attributes, you can provide `["*"]` as a wildcard: `attributesToCrop=["*"]`. This causes `_formatted` to include the cropped values of all attributes present in [`attributesToRetrieve`](#attributes-to-retrieve).
#### Cropping algorithm
Suppose you have a field containing the following string: `Donatello is a skilled and smart turtle. Leonardo is the most skilled turtle. Raphael is the strongest turtle.`
Meilisearch tries to respect sentence boundaries when cropping. For example, if your search term is `Leonardo` and your `cropLength` is 6, Meilisearch will prioritize keeping the sentence together and return: `Leonardo is the most skilled turtle.`
If a query contains only a single search term, Meilisearch crops around the first occurrence of that term. If you search for `turtle` and your `cropLength` is 7, Meilisearch will return the first instance of that word: `Donatello is a skilled and smart turtle.`
If a query contains multiple search terms, Meilisearch centers the crop around the largest number of unique matches, giving priority to terms that are closer to each other and follow the original query order. If you search for `skilled turtle` with a `cropLength` of 6, Meilisearch will return `Leonardo is the most skilled turtle`.
If Meilisearch does not find any query terms in a field, cropping begins at the first word in that field. If you search for `Michelangelo` with a `cropLength` of 4 and this string is present in another field, Meilisearch will return `Donatello is a skilled …`.
#### Example
If you use `shifu` as a search query and set the value of the `cropLength` parameter to `5`:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "shifu",
"attributesToCrop": ["overview"],
"cropLength": 5
}'
```
```javascript JS
client.index('movies').search('shifu', {
attributesToCrop: ['overview'],
cropLength: 5
})
```
```python Python
client.index('movies').search('shifu', {
'attributesToCrop': ['overview'],
'cropLength': 5
})
```
```php PHP
$client->index('movies')->search('shifu', [
'attributesToCrop' => ['overview'],
'cropLength' => 5
]);
```
```java Java
SearchRequest searchRequest =
SearchRequest.builder()
.q("shifu")
.attributesToCrop(new String[] {"overview"})
.cropLength(5)
.build();
client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('shifu', {
attributes_to_crop: ['overview'],
crop_length: 5
})
```
```go Go
resp, err := client.Index("movies").Search("shifu", &meilisearch.SearchRequest{
AttributesToCrop: []string{"overview"},
CropLength: 5,
})
```
```csharp C#
var sq = new SearchQuery
{
AttributesToCrop = new[] {"overview"},
CropLength = 5
};
await client.Index("movies").SearchAsync("shifu", sq);
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("shifu")
.with_attributes_to_crop(Selectors::Some(&[("overview", None)]))
.with_crop_length(5)
.execute()
.await
.unwrap();
// Get the formatted results
let formatted_results: Vec<&Movie> = results
.hits
.iter()
.map(|r| r.formatted_result.as_ref().unwrap())
.collect();
```
```swift Swift
let searchParameters = SearchParameters(
query: "shifu",
attributesToCrop: ["overview"],
cropLength: 5)
client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').search(
'shifu', SearchQuery(attributesToCrop: ['overview'], cropLength: 5));
```
You will get the following response with the **cropped text in the `_formatted` object**:
```json
{
"id": 50393,
"title": "Kung Fu Panda Holiday",
"poster": "https://image.tmdb.org/t/p/w1280/gp18R42TbSUlw9VnXFqyecm52lq.jpg",
"overview": "The Winter Feast is Po's favorite holiday. Every year he and his father hang decorations, cook together, and serve noodle soup to the villagers. But this year Shifu informs Po that as Dragon Warrior, it is his duty to host the formal Winter Feast at the Jade Palace. Po is caught between his obligations as the Dragon Warrior and his family traditions: between Shifu and Mr. Ping.",
"release_date": 1290729600,
"_formatted": {
"id": 50393,
"title": "Kung Fu Panda Holiday",
"poster": "https://image.tmdb.org/t/p/w1280/gp18R42TbSUlw9VnXFqyecm52lq.jpg",
"overview": "…this year Shifu informs Po…",
"release_date": 1290729600
}
}
```
### Crop length
**Parameter**: `cropLength`
**Expected value**: A positive integer
**Default value**: `10`
Configures the total number of words to appear in the cropped value when using [`attributesToCrop`](#attributes-to-crop). If `attributesToCrop` is not configured, `cropLength` has no effect on the returned results.
Query terms are counted as part of the cropped value length. If `cropLength` is set to `2` and you search for one term (for example, `shifu`), the cropped field will contain two words in total (for example, `"…Shifu informs…"`).
Stop words are also counted against this number. If `cropLength` is set to `2` and you search for one term (for example, `grinch`), the cropped result may contain a stop word (for example, `"…the Grinch…"`).
If `attributesToCrop` uses the `attributeName:number` syntax to specify a custom crop length for an attribute, that value has priority over `cropLength`.
### Crop marker
**Parameter**: `cropMarker`
**Expected value**: A string
**Default value**: `"…"`
Sets a string to mark crop boundaries when using the [`attributesToCrop`](#attributes-to-crop) parameter. The crop marker will be inserted on both sides of the crop. If `attributesToCrop` is not configured, `cropMarker` has no effect on the returned search results.
If `cropMarker` is set to `null` or an empty string, no markers will be included in the returned results.
Crop markers are only added where content has been removed. For example, if the cropped text includes the first word of the field value, the crop marker will not be added to the beginning of the cropped result.
#### Example
When searching for `shifu`, you can use `cropMarker` to change the default `…`:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "shifu",
"cropMarker": "[…]",
"attributesToCrop": ["overview"]
}'
```
```javascript JS
client.index('movies').search('shifu', {
attributesToCrop: ['overview'],
cropMarker: '[…]'
})
```
```python Python
client.index('movies').search('shifu', {
'attributesToCrop': ['overview'],
'cropMarker': '[…]'
})
```
```php PHP
$client->index('movies')->search('shifu', [
'attributesToCrop' => ['overview'],
'cropMarker' => '[…]'
]);
```
```java Java
SearchRequest searchRequest =
SearchRequest.builder()
.q("shifu")
.attributesToCrop(new String[] {"overview"})
.cropMarker("[…]")
.build();
client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('shifu', {
attributes_to_crop: ['overview'],
crop_marker: '[…]'
})
```
```go Go
resp, err := client.Index("movies").Search("shifu", &meilisearch.SearchRequest{
AttributesToCrop: []string{"overview"},
CropMarker: "[…]",
})
```
```csharp C#
var sq = new SearchQuery
{
AttributesToCrop = new[] {"overview"},
CropMarker = "[...]"
};
await client.Index("movies").SearchAsync("shifu", sq);
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("shifu")
.with_attributes_to_crop(Selectors::Some(&[("overview", None)]))
.with_crop_marker("[…]")
.execute()
.await
.unwrap();
// Get the formatted results
let formatted_results: Vec<&Movie> = results
.hits
.iter()
.map(|r| r.formatted_result.as_ref().unwrap())
.collect();
```
```swift Swift
let searchParameters = SearchParameters(
query: "shifu",
attributesToCrop: ["overview"],
cropMarker: "[…]")
client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').search(
'shifu',
SearchQuery(
attributesToCrop: ['overview'],
cropMarker: '[…]',
),
);
```
```json
{
"id": 50393,
"title": "Kung Fu Panda Holiday",
"poster": "https://image.tmdb.org/t/p/w1280/gp18R42TbSUlw9VnXFqyecm52lq.jpg",
"overview": "The Winter Feast is Po's favorite holiday. Every year he and his father hang decorations, cook together, and serve noodle soup to the villagers. But this year Shifu informs Po that as Dragon Warrior, it is his duty to host the formal Winter Feast at the Jade Palace. Po is caught between his obligations as the Dragon Warrior and his family traditions: between Shifu and Mr. Ping.",
"release_date": 1290729600,
"_formatted": {
"id": 50393,
"title": "Kung Fu Panda Holiday",
"poster": "https://image.tmdb.org/t/p/w1280/gp18R42TbSUlw9VnXFqyecm52lq.jpg",
"overview": "[…]But this year Shifu informs Po that as Dragon Warrior,[…]",
"release_date": 1290729600
}
}
```
### Attributes to highlight
**Parameter**: `attributesToHighlight`
**Expected value**: An array of attributes or `["*"]`
**Default value**: `null`
Highlights matching query terms in the specified attributes. `attributesToHighlight` only works on values of the following types: string, number, array, object.
When this parameter is set, returned documents include a `_formatted` object containing the highlighted terms.
Instead of a list of attributes, you can use `["*"]`: `attributesToHighlight=["*"]`. In this case, all the attributes present in [`attributesToRetrieve`](#attributes-to-retrieve) will be assigned to `attributesToHighlight`.
By default highlighted elements are enclosed in `` and `` tags. You may change this by using the [`highlightPreTag` and `highlightPostTag` search parameters](#highlight-tags).
`attributesToHighlight` also highlights terms configured as [synonyms](/reference/api/settings#synonyms) and [stop words](/reference/api/settings#stop-words).
`attributesToHighlight` will highlight matches within all attributes added to the `attributesToHighlight` array, even if those attributes are not set as [`searchableAttributes`](/learn/relevancy/displayed_searchable_attributes#searchable-fields).
#### Example
The following query highlights matches present in the `overview` attribute:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "winter feast",
"attributesToHighlight": ["overview"]
}'
```
```javascript JS
client.index('movies').search('winter feast', {
attributesToHighlight: ['overview']
})
```
```python Python
client.index('movies').search('winter feast', {
'attributesToHighlight': ['overview']
})
```
```php PHP
$client->index('movies')->search('winter feast', [
'attributesToHighlight' => ['overview']
]);
```
```java Java
SearchRequest searchRequest =
new SearchRequest("winter feast").setAttributesToHighlight(new String[] {"overview"});
client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('winter feast', {
attributes_to_highlight: ['overview']
})
```
```go Go
resp, err := client.Index("movies").Search("winter feast", &meilisearch.SearchRequest{
AttributesToHighlight: []string{"overview"},
})
```
```csharp C#
var sq = new SearchQuery
{
AttributesToHighlight = new[] {"overview"}
};
await client.Index("movies").SearchAsync("winter feast", sq);
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("winter feast")
.with_attributes_to_highlight(Selectors::Some(&["overview"]))
.execute()
.await
.unwrap();
// Get the formatted results
let formatted_results: Vec<&Movie> = results
.hits
.iter()
.map(|r| r.formatted_result.as_ref().unwrap())
.collect();
```
```swift Swift
let searchParameters = SearchParameters(
query: "winter feast",
attributesToHighlight: ["overview"])
client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').search(
'winter feast', SearchQuery(attributesToHighlight: ['overview']));
```
The highlighted version of the text would then be found in the `_formatted` object included in each returned document:
```json
{
"id": 50393,
"title": "Kung Fu Panda Holiday",
"poster": "https://image.tmdb.org/t/p/w1280/gp18R42TbSUlw9VnXFqyecm52lq.jpg",
"overview": "The Winter Feast is Po's favorite holiday. Every year he and his father hang decorations, cook together, and serve noodle soup to the villagers. But this year Shifu informs Po that as Dragon Warrior, it is his duty to host the formal Winter Feast at the Jade Palace. Po is caught between his obligations as the Dragon Warrior and his family traditions: between Shifu and Mr. Ping.",
"release_date": 1290729600,
"_formatted": {
"id": 50393,
"title": "Kung Fu Panda Holiday",
"poster": "https://image.tmdb.org/t/p/w1280/gp18R42TbSUlw9VnXFqyecm52lq.jpg",
"overview": "The Winter Feast is Po's favorite holiday. Every year he and his father hang decorations, cook together, and serve noodle soup to the villagers. But this year Shifu informs Po that as Dragon Warrior, it is his duty to host the formal Winter Feast at the Jade Palace. Po is caught between his obligations as the Dragon Warrior and his family traditions: between Shifu and Mr. Ping.",
"release_date": 1290729600
}
}
```
### Highlight tags
**Parameters**: `highlightPreTag` and `highlightPostTag`
**Expected value**: A string
**Default value**: `""` and `""` respectively
`highlightPreTag` and `highlightPostTag` configure, respectively, the strings to be inserted before and after a word highlighted by `attributesToHighlight`. If `attributesToHighlight` has not been configured, `highlightPreTag` and `highlightPostTag` have no effect on the returned search results.
It is possible to use `highlightPreTag` and `highlightPostTag` to enclose terms between any string of text, not only HTML tags: `""`, `""`, `"*"`, and `"__"` are all equally supported values.
If `highlightPreTag` or `highlightPostTag` are set to `null` or an empty string, nothing will be inserted respectively at the beginning or the end of a highlighted term.
#### Example
The following query encloses highlighted matches in `` tags with a `class` attribute:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "winter feast",
"attributesToHighlight": ["overview"],
"highlightPreTag": "",
"highlightPostTag": ""
}'
```
```javascript JS
client.index('movies').search('winter feast', {
attributesToHighlight: ['overview'],
highlightPreTag: '',
highlightPostTag: ''
})
```
```python Python
client.index('movies').search('winter feast', {
'attributesToHighlight': ['overview'],
'highlightPreTag': '',
'highlightPostTag': ''
})
```
```php PHP
$client->index('movies')->search('winter feast', [
'attributesToHighlight' => ['overview'],
'highlightPreTag' => '',
'highlightPostTag' => ''
]);
```
```java Java
SearchRequest searchRequest =
SearchRequest.builder()
.q("winter feast")
.attributesToHighlight(new String[] {"overview"})
.highlightPreTag("")
.highlightPostTag("")
.build();
client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('winter feast', {
attributes_to_highlight: ['overview'],
highlight_pre_tag: '',
highlight_post_tag: ''
})
```
```go Go
resp, err := client.Index("movies").Search("winter feast", &meilisearch.SearchRequest{
AttributesToHighlight: []string{"overview"},
HighlightPreTag: "",
HighlightPostTag: "",
})
```
```csharp C#
var sq = new SearchQuery
{
AttributesToHighlight = new[] {"overview"},
HighlightPreTag = "",
HighlightPostTag = ""
};
await client.Index("movies").SearchAsync("winter feast", sq);
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("winter feast")
.with_attributes_to_highlight(Selectors::Some(&["overview"]))
.with_highlight_pre_tag("")
.with_highlight_post_tag("")
.execute()
.await
.unwrap();
// Get the formatted results
let formatted_results: Vec<&Movie> = results
.hits
.iter()
.map(|r| r.formatted_result.as_ref().unwrap())
.collect();
```
```swift Swift
let searchParameters = SearchParameters(
query: "winter feast",
attributesToHighlight: ["overview"],
highlightPreTag: "",
highlightPostTag: "")
client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').search(
'winter feast',
SearchQuery(
attributesToHighlight: ['overview'],
highlightPreTag: '',
highlightPostTag: '',
),
);
```
You can find the highlighted query terms inside the `_formatted` property:
```json
{
"id": 50393,
"title": "Kung Fu Panda Holiday",
"poster": "https://image.tmdb.org/t/p/w1280/gp18R42TbSUlw9VnXFqyecm52lq.jpg",
"overview": "The Winter Feast is Po's favorite holiday. Every year he and his father hang decorations, cook together, and serve noodle soup to the villagers. But this year Shifu informs Po that as Dragon Warrior, it is his duty to host the formal Winter Feast at the Jade Palace. Po is caught between his obligations as the Dragon Warrior and his family traditions: between Shifu and Mr. Ping.",
"release_date": 1290729600,
"_formatted": {
"id": 50393,
"title": "Kung Fu Panda Holiday",
"poster": "https://image.tmdb.org/t/p/w1280/gp18R42TbSUlw9VnXFqyecm52lq.jpg",
"overview": "The Winter Feast is Po's favorite holiday. Every year he and his father hang decorations, cook together, and serve noodle soup to the villagers. But this year Shifu informs Po that as Dragon Warrior, it is his duty to host the formal Winter Feast at the Jade Palace. Po is caught between his obligations as the Dragon Warrior and his family traditions: between Shifu and Mr. Ping.",
"release_date": 1290729600
}
}
```
Though it is not necessary to use `highlightPreTag` and `highlightPostTag` in conjunction, be careful to ensure tags are correctly matched. In the above example, not setting `highlightPostTag` would result in malformed HTML: `Winter Feast`.
### Show matches position
**Parameter**: `showMatchesPosition`
**Expected value**: `true` or `false`
**Default value**: `false`
Adds a `_matchesPosition` object to the search response that contains the location of each occurrence of queried terms across all fields. This is useful when you need more control than offered by our [built-in highlighting](#attributes-to-highlight). `showMatchesPosition` only works for strings, numbers, and arrays of strings and numbers.
`showMatchesPosition` returns the location of matched query terms within all attributes, even attributes that are not set as [`searchableAttributes`](/learn/relevancy/displayed_searchable_attributes#searchable-fields).
The beginning of a matching term within a field is indicated by `start`, and its length by `length`.
`start` and `length` are measured in bytes and not the number of characters. For example, `ü` represents two bytes but one character.
#### Example
If you set `showMatchesPosition` to `true` and search for `winter feast`:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "winter feast",
"showMatchesPosition": true
}'
```
```javascript JS
client.index('movies').search('winter feast', {
showMatchesPosition: true
})
```
```python Python
client.index('movies').search('winter feast', {
'showMatchesPosition': True
})
```
```php PHP
$client->index('movies')->search('winter feast', [
'attributesToHighlight' => ['overview'],
'showMatchesPosition' => true
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("winter feast").showMatchesPosition(true).build();
SearchResultPaginated searchResult = client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('winter feast', {
show_matches_position: true
})
```
```go Go
resp, err := client.Index("movies").Search("winter feast", &meilisearch.SearchRequest{
ShowMatchesPosition: true,
})
```
```csharp C#
await client.Index("movies").SearchAsync(
"winter feast",
new SearchQuery
{
ShowMatchesPosition = True,
});
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("winter feast")
.with_show_matches_position(true)
.execute()
.await
.unwrap();
// Get the matches info
let matches_position: Vec<&HashMap>> = results
.hits
.iter()
.map(|r| r.matches_position.as_ref().unwrap())
.collect();
```
```swift Swift
let searchParameters = SearchParameters(
query: "winter feast",
showMatchesPosition: true)
client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client
.index('movies')
.search('winter feast', SearchQuery(showMatchesPosition: true));
```
You would get the following response with **information about the matches in the `_matchesPosition` object**. Note how Meilisearch searches for `winter` and `feast` separately because of the whitespace:
```json
{
"id": 50393,
"title": "Kung Fu Panda Holiday",
"poster": "https://image.tmdb.org/t/p/w500/rV77WxY35LuYLOuQvBeD1nyWMuI.jpg",
"overview": "The Winter Feast is Po's favorite holiday. Every year he and his father hang decorations, cook together, and serve noodle soup to the villagers. But this year Shifu informs Po that as Dragon Warrior, it is his duty to host the formal Winter Feast at the Jade Palace. Po is caught between his obligations as the Dragon Warrior and his family traditions: between Shifu and Mr. Ping.",
"release_date": 1290729600,
"_matchesPosition": {
"overview": [
{
"start": 4,
"length": 6
},
{
"start": 11,
"length": 5
},
{
"start": 234,
"length": 6
},
{
"start": 241,
"length": 5
}
]
}
}
```
### Sort
**Parameter**: `sort`
**Expected value**: A list of attributes written as an array or as a comma-separated string
**Default value**: `null`
Sorts search results at query time according to the specified attributes and indicated order.
Each attribute in the list must be followed by a colon (`:`) and the preferred sorting order: either ascending (`asc`) or descending (`desc`).
Attribute order is meaningful. The first attributes in a list will be given precedence over those that come later.
For example, `sort="price:asc,author:desc` will prioritize `price` over `author` when sorting results.
When using the `POST` route, `sort` expects an array of strings.
When using the `GET` route, `sort` expects the list as a comma-separated string.
[Read more about sorting search results in our dedicated guide.](/learn/filtering_and_sorting/sort_search_results)
#### Example
You can search for science fiction books ordered from cheapest to most expensive:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/books/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "science fiction",
"sort": ["price:asc"]
}'
```
```javascript JS
client.index('books').search('science fiction', {
sort: ['price:asc'],
})
```
```python Python
client.index('books').search('science fiction', {
'sort': ['price:asc']
})
```
```php PHP
$client->index('books')->search('science fiction', ['sort' => ['price:asc']]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("science fiction").sort(new String[] {"price:asc"}).build();
client.index("search_parameter_guide_sort_1").search(searchRequest);
```
```ruby Ruby
client.index('books').search('science fiction', { sort: ['price:asc'] })
```
```go Go
resp, err := client.Index("books").Search("science fiction", &meilisearch.SearchRequest{
Sort: []string{
"price:asc",
},
})
```
```csharp C#
var sq = new SearchQuery
{
Sort = new[] { "price:asc" },
};
await client.Index("books").SearchAsync("science fiction", sq);
```
```rust Rust
let results: SearchResults = client
.index("books")
.search()
.with_query("science fiction")
.with_sort(&["price:asc"])
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "science fiction",
sort: ["price:asc"]
)
client.index("books").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client
.index('books')
.search('science fiction', SearchQuery(sort: ['price:asc']));
```
#### Sorting results with `_geoPoint`
When dealing with documents containing geolocation data, you can use `_geoPoint` to sort results based on their distance from a specific geographic location.
`_geoPoint` is a sorting function that requires two floating point numbers indicating a location's latitude and longitude. You must also specify whether the sort should be ascending (`asc`) or descending (`desc`):
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/restaurants/search' \
-H 'Content-type:application/json' \
--data-binary '{ "sort": ["_geoPoint(48.8561446,2.2978204):asc"] }'
```
```javascript JS
client.index('restaurants').search('', {
sort: ['_geoPoint(48.8561446, 2.2978204):asc'],
})
```
```python Python
client.index('restaurants').search('', {
'sort': ['_geoPoint(48.8561446,2.2978204):asc']
})
```
```php PHP
$client->index('restaurants')->search('', [
'sort' => ['_geoPoint(48.8561446,2.2978204):asc']
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("").sort(new String[] {"_geoPoint(48.8561446,2.2978204):asc"}).build();
client.index("restaurants").search(searchRequest);
```
```ruby Ruby
client.index('restaurants').search('', { sort: ['_geoPoint(48.8561446, 2.2978204):asc'] })
```
```go Go
resp, err := client.Index("restaurants").Search("", &meilisearch.SearchRequest{
Sort: []string{
"_geoPoint(48.8561446,2.2978204):asc",
},
})
```
```csharp C#
SearchQuery filters = new SearchQuery()
{
Sort = new string[] { "_geoPoint(48.8561446,2.2978204):asc" }
};
var restaurants = await client.Index("restaurants").SearchAsync("", filters);
```
```rust Rust
let results: SearchResults = client
.index("restaurants")
.search()
.with_sort(&["_geoPoint(48.8561446, 2.2978204):asc"])
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(
query: "",
sort: ["_geoPoint(48.8561446, 2.2978204):asc"]
)
client.index("restaurants").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('restaurants').search(
'', SearchQuery(sort: ['_geoPoint(48.8561446, 2.2978204):asc']));
```
Queries using `_geoPoint` will always include a `geoDistance` field containing the distance in meters between the document location and the `_geoPoint`:
```json
[
{
"id": 1,
"name": "Nàpiz' Milano",
"_geo": {
"lat": 45.4777599,
"lng": 9.1967508
},
"_geoDistance": 1532
}
]
```
[You can read more about location-based sorting in our dedicated guide.](/learn/filtering_and_sorting/geosearch#sorting-results-with-_geopoint)
### Matching strategy
**Parameter**: `matchingStrategy`
**Expected value**: `last`, `all`, or `frequency`
**Default value**: `last`
Defines the strategy used to match query terms in documents.
#### `last`
`last` returns documents containing all the query terms first. If there are not enough results containing all query terms to meet the requested `limit`, Meilisearch will remove one query term at a time, starting from the end of the query.
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "big fat liar",
"matchingStrategy": "last"
}'
```
```javascript JS
client.index('movies').search('big fat liar', {
matchingStrategy: 'last'
})
```
```python Python
client.index('movies').search('big fat liar', {
'matchingStrategy': 'last'
})
```
```php PHP
$client->index('movies')->search('big fat liar', ['matchingStrategy' => 'last']);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("big fat liar").matchingStrategy(MatchingStrategy.LAST).build();
client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('big fat liar', {
matching_strategy: 'last'
})
```
```go Go
resp, err := client.Index("movies").Search("big fat liar", &meilisearch.SearchRequest{
MatchingStrategy: Last,
})
```
```csharp C#
SearchQuery params = new SearchQuery() { MatchingStrategy = "last" };
await client.Index("movies").SearchAsync("big fat liar", params);
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("big fat liar")
.with_matching_strategy(MatchingStrategies::LAST)
.execute()
.await
.unwrap();
```
```dart Dart
await client.index('movies').search(
'big fat liar', SearchQuery(matchingStrategy: MatchingStrategy.last));
```
With the above code sample, Meilisearch will first return documents that contain all three words. If the results don't meet the requested `limit`, it will also return documents containing only the first two terms, `big fat`, followed by documents containing only `big`.
#### `all`
`all` only returns documents that contain all query terms. Meilisearch will not match any more documents even if there aren't enough to meet the requested `limit`.
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "big fat liar",
"matchingStrategy": "all"
}'
```
```javascript JS
client.index('movies').search('big fat liar', {
matchingStrategy: 'all'
})
```
```python Python
client.index('movies').search('big fat liar', {
'matchingStrategy': 'all'
})
```
```php PHP
$client->index('movies')->search('big fat liar', ['matchingStrategy' => 'all']);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("big fat liar").matchingStrategy(MatchingStrategy.ALL).build();
client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('big fat liar', {
matching_strategy: 'all'
})
```
```go Go
resp, err := client.Index("movies").Search("big fat liar", &meilisearch.SearchRequest{
MatchingStrategy: All,
})
```
```csharp C#
SearchQuery params = new SearchQuery() { MatchingStrategy = "all" };
await client.Index("movies").SearchAsync("big fat liar", params);
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("big fat liar")
.with_matching_strategy(MatchingStrategies::ALL)
.execute()
.await
.unwrap();
```
```dart Dart
await client.index('movies').search(
'big fat liar', SearchQuery(matchingStrategy: MatchingStrategy.all));
```
The above code sample would only return documents containing all three words.
#### `frequency`
`frequency` returns documents containing all the query terms first. If there are not enough results containing all query terms to meet the requested limit, Meilisearch will remove one query term at a time, starting with the word that is the most frequent in the dataset. `frequency` effectively gives more weight to terms that appear less frequently in a set of results.
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "white shirt",
"matchingStrategy": "frequency"
}'
```
```javascript JS
client.index('movies').search('white shirt', {
matchingStrategy: 'frequency'
})
```
```python Python
client.index('movies').search('big fat liar', {
'matchingStrategy': 'frequency'
})
```
```php PHP
$client->index('movies')->search('white shirt', ['matchingStrategy' => 'frequency']);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("white shirt").matchingStrategy(MatchingStrategy.FREQUENCY).build();
client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('white shirt', {
matching_strategy: 'frequency'
})
```
```go Go
client.Index("movies").Search("white shirt", &meilisearch.SearchRequest{
MatchingStrategy: Frequency,
})
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("white shirt")
.with_matching_strategy(MatchingStrategies::FREQUENCY)
.execute()
.await
.unwrap();
```
In a dataset where many documents contain the term `"shirt"`, the above code sample would prioritize documents containing `"white"`.
### Ranking score
**Parameter**: `showRankingScore`
**Expected value**: `true` or `false`
**Default value**: `false`
Adds a global ranking score field, `_rankingScore`, to each document. The `_rankingScore` is a numeric value between `0.0` and `1.0`. The higher the `_rankingScore`, the more relevant the document.
The `sort` ranking rule does not influence the `_rankingScore`. Instead, the document order is determined by the value of the field they are sorted on.
A document's ranking score does not change based on the scores of other documents in the same index.
For example, if a document A has a score of `0.5` for a query term, this value remains constant no matter the score of documents B, C, or D.
#### Example
The code sample below returns the `_rankingScore` when searching for `dragon` in `movies`:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "dragon",
"showRankingScore": true
}'
```
```javascript JS
client.index('movies').search('dragon', {
showRankingScore: true
})
```
```python Python
client.index('movies').search('dragon', {
'showRankingScore': True
})
```
```php PHP
$client->index('movies')->search('dragon', [
'showRankingScore' => true
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("dragon").showRankingScore(true).build();
client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('dragon', {
show_ranking_score: true
})
```
```go Go
resp, err := client.Index("movies").Search("dragon", &meilisearch.SearchRequest{
showRankingScore: true,
})
```
```csharp C#
var params = new SearchQuery()
{
ShowRankingScore = true
};
await client.Index("movies").SearchAsync("dragon", params);
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("dragon")
.with_show_ranking_score(true)
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(query: "dragon", showRankingScore: true)
client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult.rankingScore)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client
.index('movies')
.search('dragon', SearchQuery(showRankingScore: true));
```
```json
{
"hits": [
{
"id": 31072,
"title": "Dragon",
"overview": "In a desperate attempt to save her kingdom…",
…
"_rankingScore": 0.92
},
{
"id": 70057,
"title": "Dragon",
"overview": "A sinful martial arts expert wants…",
…
"_rankingScore": 0.91
},
…
],
…
}
```
### Ranking score details
**Parameter**: `showRankingScoreDetails`
**Expected value**: `true` or `false`
**Default value**: `false`
Adds a detailed global ranking score field, `_rankingScoreDetails`, to each document. `_rankingScoreDetails` is an object containing a nested object for each active ranking rule.
#### Ranking score details object
Each ranking rule details its score in its own object. Fields vary per ranking rule.
##### `words`
* `order`: order in which this ranking rule was applied
* `score`: ranking score for this rule
* `matchingWords`: number of words in the query that match in the document
* `maxMatchingWords`: maximum number of words in the query that can match in the document
##### `typo`
* `order`: order in which this specific ranking rule was applied
* `score`: ranking score for this rule
* `typoCount`: number of typos corrected so that the document matches the query term
* `maxTypoCount`: maximum number of typos accepted
##### `proximity`
* `order`: order in which this ranking rule was applied
* `score`: ranking score for this rule
##### `attribute`
* `order`: order in which this ranking rule was applied
* `score`: ranking score for this rule
* `attributeRankingOrderScore`: score computed from the maximum attribute ranking order for the matching attributes
* `queryWordDistanceScore`: score computed from the distance between the position words in the query and the position of words in matched attributes
##### `exactness`
* `order`: order in which this ranking rule was applied
* `score`: ranking score for this rule
* `matchType`: either `exactMatch`, `matchesStart`, or `noExactMatch`:
* `exactMatch`: document contains an attribute matching all query terms with no other words between them and in the order they were given
* `matchesStart`: document contains an attribute with all query terms in the same order as the original query
* `noExactMatch`: document contains an attribute with at least one query term matching the original query
* `matchingWords`: the number of exact matches in an attribute when `matchType` is `noExactMatch`
* `maxMatchingWords`: the maximum number of exact matches in an attribute when `matchType` is `noExactMatch`
##### `field_name:direction`
The `sort` ranking rule does not appear as a single field in the score details object. Instead, each sorted attribute appears as its own field, followed by a colon (`:`) and the sorting direction: `attribute:direction`.
* `order`: order in which this ranking rule was applied
* `value`: value of the field used for sorting
##### `_geoPoint(lat:lng):direction`
* `order`: order in which this ranking rule was applied
* `value`: value of the field used for sorting
* `distance`: same as [\_geoDistance](/learn/filtering_and_sorting/geosearch#finding-the-distance-between-a-document-and-a-_geopoint)
##### `vectorSort(target_vector)`
* `order`: order in which this specific ranking rule was applied
* `value`: vector used for sorting the document
* `similarity`: similarity score between the target vector and the value vector. 1.0 means a perfect similarity, 0.0 a perfect dissimilarity
#### Example
The code sample below returns the `_rankingScoreDetail` when searching for `dragon` in `movies`:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "dragon",
"showRankingScoreDetails": true
}'
```
```javascript JS
client.index('movies').search('dragon', { showRankingScoreDetails: true })
```
```python Python
client.index('movies').search('dragon', {
'showRankingScoreDetails': True
})
```
```php PHP
$client->index('movies')->search('dragon', [
'showRankingScoreDetails' => true
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("dragon").showRankingScoreDetails(true).build();
client.index("movies").search(searchRequest);
```
```ruby Ruby
client.index('movies').search('dragon', {
show_ranking_score_details: true
})
```
```go Go
resp, err := client.Index("movies").Search("dragon", &meilisearch.SearchRequest{
showRankingScoreDetails: true,
})
```
```csharp C#
var params = new SearchQuery()
{
ShowRankingScoreDetails = true
};
await client.Index("movies").SearchAsync("dragon", params);
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("dragon")
.with_show_ranking_score_details(true)
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(query: "dragon", showRankingScoreDetails: true)
let movies: Searchable = try await client.index("movies").search(searchParameters)
```
```json
{
"hits": [
{
"id": 31072,
"title": "Dragon",
"overview": "In a desperate attempt to save her kingdom…",
…
"_rankingScoreDetails": {
"words": {
"order": 0,
"matchingWords": 4,
"maxMatchingWords": 4,
"score": 1.0
},
"typo": {
"order": 2,
"typoCount": 1,
"maxTypoCount": 4,
"score": 0.75
},
"name:asc": {
"order": 1,
"value": "Dragon"
}
}
},
…
],
…
}
```
### Ranking score threshold
**Parameter**: `rankingScoreThreshold`
**Expected value**: A number between `0.0` and `1.0`
**Default value**: `null`
Excludes results below the specified ranking score. The threshold applies to all search types including full-text search, semantic search, and hybrid search.
Excluded results do not count towards `estimatedTotalHits`, `totalHits`, and facet distribution.
Using `rankingScoreThreshold` with `page` and `hitsPerPage` forces Meilisearch to evaluate the ranking score of all matching documents to return an accurate `totalHits`. This may negatively impact search performance.
Queries with `limit` and `offset` avoid this overhead when using `rankingScoreThreshold`.
#### Example
The following query only returns results with a ranking score bigger than `0.2`:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/INDEX_NAME/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "badman",
"rankingScoreThreshold": 0.2
}'
```
```javascript JS
client.index('INDEX_NAME').search('badman', { rankingScoreThreshold: 0.2 })
```
```python Python
client.index('INDEX_NAME').search('badman', { 'rankingScoreThreshold': 0.2 })
```
```php PHP
$client->index('INDEX_NAME')->search('badman', [
'rankingScoreThreshold' => 0.2
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("badman").rankingScoreThreshold(0.2).build();
client.index("INDEX_NAME").search(searchRequest);
```
```ruby Ruby
client.index('INDEX_NAME').search('badman', {
rankingScoreThreshold: 0.2
})
```
```go Go
client.Index("INDEX_NAME").Search("badman", &meilisearch.SearchRequest{
RankingScoreThreshold: 0.2,
})
```
```rust Rust
let res = client
.index("INDEX_NAME")
.search()
.with_query("badman")
.with_ranking_score_threshold(0.2)
.execute()
.await
.unwrap();
```
### Customize attributes to search on at search time
**Parameter**: `attributesToSearchOn`
**Expected value**: A list of searchable attributes written as an array
**Default value**: `["*"]`
Configures a query to only look for terms in the specified attributes.
Instead of a list of attributes, you can pass a wildcard value (`["*"]`) and `null` to `attributesToSearchOn`. In both cases, Meilisearch will search for matches in all searchable attributes.
Attributes passed to `attributesToSearchOn` must also be present in the `searchableAttributes` list.
The order of attributes in `attributesToSearchOn` does not affect relevancy.
#### Example
The following query returns documents whose `overview` includes `"adventure"`:
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/movies/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "adventure",
"attributesToSearchOn": ["overview"]
}'
```
```javascript JS
client.index('movies').search('adventure', {
attributesToSearchOn: ['overview']
})
```
```python Python
client.index('movies').search('adventure', {
'attributesToSearchOn': ['overview']
})
```
```php PHP
$client->index('movies')->search('adventure', [
'attributesToSearchOn' => ['overview']
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("adventure").attributesToSearchOn(new String[] {"overview"});
client.index("movies").searchRequest(searchRequest);
```
```ruby Ruby
client.index('movies').search('adventure', {
attributes_to_search_on: ['overview']
})
```
```go Go
resp, err := client.Index("movies").Search("adventure", &meilisearch.SearchRequest{
AttributesToSearchOn: []string{"overview"},
})
```
```csharp C#
var searchQuery = new SearchQuery
{
AttributesToSearchOn = new[] { "overview" }
};
await client.Index("movies").SearchAsync("adventure", searchQuery);
```
```rust Rust
let results: SearchResults = client
.index("movies")
.search()
.with_query("adventure")
.with_attributes_to_search_on(&["overview"])
.execute()
.await
.unwrap();
```
```swift Swift
let searchParameters = SearchParameters(query: "adventure", attributesToSearchOn: ["overview"])
client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('books').facetSearch(
FacetSearchQuery(
facetQuery: 'c',
facetName: 'genres',
),
);
```
Results would not include documents containing `"adventure"` in other fields such as `title` or `genre`, even if these fields were present in the `searchableAttributes` list.
### Hybrid search
**Parameter**: `hybrid`
**Expected value**: An object with two fields: `embedder` and `semanticRatio`
**Default value**: `null`
Configures Meilisearch to return search results based on a query's meaning and context.
`hybrid` must be an object. It accepts two fields: `embedder` and `semanticRatio`.
`embedder` must be a string indicating an embedder configured with the `/settings` endpoint. It is mandatory to specify a valid embedder when performing AI-powered searches.
`semanticRatio` must be a number between `0.0` and `1.0` indicating the proportion between keyword and semantic search results. `0.0` causes Meilisearch to only return keyword results. `1.0` causes Meilisearch to only return meaning-based results. Defaults to `0.5`.
#### Example
```bash cURL
curl -X POST 'MEILISEARCH_URL/indexes/INDEX_NAME/search' \
-H 'content-type: application/json' \
--data-binary '{
"q": "kitchen utensils",
"hybrid": {
"semanticRatio": 0.9,
"embedder": "EMBEDDER_NAME"
}
}'
```
```javascript JS
client.index('INDEX_NAME').search('kitchen utensils', {
hybrid: {
semanticRatio: 0.9,
embedder: 'EMBEDDER_NAME'
}
})
```
```php PHP
$client->index('INDEX_NAME')->search('kitchen utensils', [
'hybrid' => [
'semanticRatio' => 0.9,
'embedder' => 'EMBEDDER_NAME'
]
]);
```
```rust Rust
let results = index
.search()
.with_query("kitchen utensils")
.with_hybrid("EMBEDDER_NAME", 0.9)
.execute()
.await
.unwrap();
```
### Vector
**Parameter**: `vector`
**Expected value**: an array of numbers
**Default value**: `null`
Use a custom vector to perform a search query. Must be an array of numbers corresponding to the dimensions of the custom vector.
`vector` is mandatory when performing searches with `userProvided` embedders. You may also use `vector` to override an embedder's automatic vector generation.
`vector` dimensions must match the dimensions of the embedder.
If a query does not specify `q`, but contains both `vector` and `hybrid.semanticRatio` bigger than `0`, Meilisearch performs a pure semantic search.
If `q` is missing and `semanticRatio` is explicitly set to `0`, Meilisearch performs a placeholder search without any vector search results.
#### Example
```bash cURL
curl -X POST 'MEILISEARCH_URL/indexes/INDEX_NAME/search' \
-H 'content-type: application/json' \
--data-binary '{
"vector": [0, 1, 2],
"hybrid": {
"embedder": "EMBEDDER_NAME"
}
}'
```
```rust Rust
let results = index
.search()
.with_vector(&[0.0, 1.0, 2.0])
.with_hybrid("EMBEDDER_NAME", 1.0)
.execute()
.await
.unwrap();
```
### Display `_vectors` in response
**Parameter**: `retrieveVectors`
**Expected value**: `true` or `false`
**Default value**: `false`
Return document and query embeddings with search results. If `true`, Meilisearch will display vector data in each [document's `_vectors` field](/reference/api/documents#_vectors).
#### Example
```bash cURL
curl -X POST 'MEILISEARCH_URL/indexes/INDEX_NAME/search' \
-H 'content-type: application/json' \
--data-binary '{
"q": "kitchen utensils",
"retrieveVectors": true,
"hybrid": {
"embedder": "EMBEDDER_NAME"
}
}'
```
```javascript JS
client.index('INDEX_NAME').search('kitchen utensils', {
retrieveVectors: true,
hybrid: {
embedder: 'EMBEDDER_NAME'
}
})
```
```php PHP
$client->index('INDEX_NAME')->search('kitchen utensils', [
'retrieveVectors' => true,
'hybrid' => [
'embedder': 'EMBEDDER_NAME'
]
]);
```
```rust Rust
let results = index
.search()
.with_query("kitchen utensils")
.with_retrieve_vectors(true)
.with_hybrid("EMBEDDER_NAME", 0.5)
.execute()
.await
.unwrap();
```
```json
{
"hits": [
{
"id": 0,
"title": "DOCUMENT NAME",
"_vectors": {
"default": {
"embeddings": [0.1, 0.2, 0.3],
"regenerate": true
}
}
…
},
…
],
…
}
```
### Query locales
**Parameter**: `locales`
**Expected value**: array of [supported ISO-639 locales](/reference/api/settings#localized-attributes-object)
**Default value**: `[]`
By default, Meilisearch auto-detects the language of a query. Use this parameter to explicitly state the language of a query.
In case of a mismatch between `locales` and the [localized attributes index setting](/reference/api/settings#localized-attributes), this parameter takes precedence.
`locales` and [`localizedAttributes`](/reference/api/settings#localized-attributes) have the same goal: explicitly state the language used in a search when Meilisearch's language auto-detection is not working as expected.
If you believe Meilisearch is detecting incorrect languages because of the query text, explicitly set the search language with `locales`.
If you believe Meilisearch is detecting incorrect languages because of document, explicitly set the document language at the index level with `localizedAttributes`.
For full control over the way Meilisearch detects languages during indexing and at search time, set both `locales` and `localizedAttributes`.
#### Example
```bash cURL
curl \
-X POST 'MEILISEARCH_URL/indexes/INDEX_NAME/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "QUERY TEXT IN JAPANESE",
"locales": ["jpn"]
}'
```
```javascript JS
client.index('INDEX_NAME').search('QUERY TEXT IN JAPANESE', { locales: ['jpn'] })
```
```python Python
client.index('INDEX_NAME').search('進撃の巨人', { 'locales': ['jpn'] })
```
```php PHP
$client->index('INDEX_NAME')->search('QUERY TEXT IN JAPANESE', [
'locales' => ['jpn']
]);
```
```java Java
SearchRequest searchRequest = SearchRequest.builder().q("QUERY TEXT IN JAPANESE").locales(new String[]{"jpn"}).build();
client.index("INDEX_NAME").search(searchRequest);
```
```ruby Ruby
client.index('INDEX_NAME').search('進撃の巨人', { locales: ['jpn'] })
```
```go Go
client.index("INDEX_NAME").Search("QUERY TEXT IN JAPANESE", &meilisearch.SearchRequest{
Locales: []string{"jpn"}
})
```
```rust Rust
let res = client
.index("books")
.search()
.with_query("進撃の巨人")
.with_locales(&["jpn"])
.execute()
.await
.unwrap();
```
```json
{
"hits": [
{
"id": 0,
"title": "DOCUMENT NAME",
"overview_jp": "OVERVIEW TEXT IN JAPANESE"
}
…
],
…
}
```
### Media
**Parameter**: `media`
**Expected value**: Object
**Default value**: `null`
This is an experimental feature. Use the Meilisearch Cloud UI or the experimental features endpoint to activate it:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{
"multimodal": true
}'
```
Specifies data to populate search fragments when performing multimodal searches.
`media` must be an object whose fields must correspond to the data required by one [search fragment](/reference/api/settings#searchfragments). `media` must match a single search fragment. If `media` matches more than one fragment or no search fragments at all, Meilisearch will return an error.
It is mandatory to specify an embedder when using `media`. `media` is incompatible with `vector`.
#### Example
```json
{
"hits": [
{
"id": 0,
"title": "DOCUMENT NAME",
…
}
…
],
…
}
```
# Settings
Source: https://www.meilisearch.com/docs/reference/api/settings
The /settings route allows you to customize search settings for the given index.
export const NoticeTag = ({label}) =>
{label}
;
export const RouteHighlighter = ({method, path}) =>
{method}
{path}
;
Use the `/settings` route to customize search settings for a given index. You can either modify all index settings at once using the [update settings endpoint](#update-settings), or use a child route to configure a single setting.
For a conceptual overview of index settings, refer to the [indexes explanation](/learn/getting_started/indexes#index-settings). To learn more about the basics of index configuration, refer to the [index configuration tutorial](/learn/configuration/configuring_index_settings_api).
## Settings interface
[Meilisearch Cloud](https://meilisearch.com/cloud) offers a [user-friendly graphical interface for managing index settings](/learn/configuration/configuring_index_settings) in addition to the `/settings` route. The Cloud interface offers more immediate and visible feedback, and is helpful for tweaking relevancy when used in conjunction with the [search preview](/learn/getting_started/search_preview).
## Settings object
By default, the settings object looks like this. All fields are modifiable.
```json
{
"displayedAttributes": [
"*"
],
"searchableAttributes": [
"*"
],
"filterableAttributes": [],
"sortableAttributes": [],
"rankingRules":
[
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness"
],
"stopWords": [],
"nonSeparatorTokens": [],
"separatorTokens": [],
"dictionary": [],
"synonyms": {},
"distinctAttribute": null,
"typoTolerance": {
"enabled": true,
"minWordSizeForTypos": {
"oneTypo": 5,
"twoTypos": 9
},
"disableOnWords": [],
"disableOnAttributes": []
},
"faceting": {
"maxValuesPerFacet": 100
},
"pagination": {
"maxTotalHits": 1000
},
"proximityPrecision": "byWord",
"facetSearch": true,
"prefixSearch": "indexingTime",
"searchCutoffMs": null,
"embedders": {},
"chat": {},
"vectorStore": "stable"
}
```
## All settings
This route allows you to retrieve, configure, or reset all of an index's settings at once.
### Get settings
Get the settings of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/movies/settings'
```
```javascript JS
client.index('movies').getSettings()
```
```python Python
client.index('movies').get_settings()
```
```php PHP
$client->index('movies')->getSettings();
```
```java Java
client.index("movies").getSettings();
```
```ruby Ruby
client.index('movies').settings
```
```go Go
client.Index("movies").GetSettings()
```
```csharp C#
await client.Index("movies").GetSettingsAsync();
```
```rust Rust
let settings: Settings = client
.index("movies")
.get_settings()
.await
.unwrap();
```
```swift Swift
client.index("movies").getSettings { (result) in
switch result {
case .success(let setting):
print(setting)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').getSettings();
```
##### Response: `200 Ok`
```json
{
"displayedAttributes": [
"*"
],
"searchableAttributes": [
"*"
],
"filterableAttributes": [],
"sortableAttributes": [],
"rankingRules":
[
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness"
],
"stopWords": [],
"nonSeparatorTokens": [],
"separatorTokens": [],
"dictionary": [],
"synonyms": {},
"distinctAttribute": null,
"typoTolerance": {
"enabled": true,
"minWordSizeForTypos": {
"oneTypo": 5,
"twoTypos": 9
},
"disableOnWords": [],
"disableOnAttributes": []
},
"faceting": {
"maxValuesPerFacet": 100
},
"pagination": {
"maxTotalHits": 1000
},
"proximityPrecision": "byWord",
"facetSearch": true,
"prefixSearch": "indexingTime",
"searchCutoffMs": null,
"embedders": {},
"chat": {},
"vectorStore": "stable"
}
```
### Update settings
Update the settings of an index.
Passing `null` to an index setting will reset it to its default value.
Updates in the settings route are **partial**. This means that any parameters not provided in the body will be left unchanged.
If the provided index does not exist, it will be created.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
| Name | Type | Default value | Description |
| :----------------------------------------------------------------------------------------- | :-------------------------- | :---------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------- |
| **[`dictionary`](#dictionary)** | Array of strings | Empty | List of strings Meilisearch should parse as a single term |
| **[`displayedAttributes`](#displayed-attributes)** | Array of strings | All attributes: `["*"]` | Fields displayed in the returned documents |
| **[`distinctAttribute`](#distinct-attribute)** | String | `null` | Search returns documents with distinct (different) values of the given field |
| **[`faceting`](#faceting)** | Object | [Default object](#faceting-object) | Faceting settings |
| **[`filterableAttributes`](#filterable-attributes)** | Array of strings or objects | Empty | Attributes to use as filters and facets |
| **[`pagination`](#pagination)** | Object | [Default object](#pagination-object) | Pagination settings |
| **[`proximityPrecision`](#proximity-precision)** | String | `"byWord"` | Precision level when calculating the proximity ranking rule |
| **[`facetSearch`](#facet-search)** | Boolean | `true` | Enable or disable [facet search](/reference/api/facet_search) functionality |
| **[`prefixSearch`](#prefix-search)** | String | `"indexingTime"` | When Meilisearch should return results only matching the beginning of query |
| **[`rankingRules`](#ranking-rules)** | Array of strings | `["words",` `"typo",` `"proximity",` `"attribute",` `"sort",` `"exactness"]` | List of ranking rules in order of importance |
| **[`searchableAttributes`](#searchable-attributes)** | Array of strings | All attributes: `["*"]` | Fields in which to search for matching query words sorted by order of importance |
| **[`searchCutoffMs`](#search-cutoff)** | Integer | `null`, or 1500ms | Maximum duration of a search query |
| **[`separatorTokens`](#separator-tokens)** | Array of strings | Empty | List of characters delimiting where one term begins and ends |
| **[`nonSeparatorTokens`](#non-separator-tokens)** | Array of strings | Empty | List of characters not delimiting where one term begins and ends |
| **[`sortableAttributes`](#sortable-attributes)** | Array of strings | Empty | Attributes to use when sorting search results |
| **[`stopWords`](#stop-words)** | Array of strings | Empty | List of words ignored by Meilisearch when present in search queries |
| **[`synonyms`](#synonyms)** | Object | Empty | List of associated words treated similarly |
| **[`typoTolerance`](#typo-tolerance)** | Object | [Default object](#typo-tolerance-object) | Typo tolerance settings |
| **[`embedders`](#embedders)** | Object of objects | [Default object](#embedders-object) | Embedder required for performing meaning-based search queries |
| **[`chat`](#conversation)** | Object | [Default object](#chat-object) | Chat settings for performing conversation-based queries |
| **[`vector-store`](#vector-store)** | String | `"stable"` | Vector store used for AI-powered search |
#### Example
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/indexes/movies/settings' \
-H 'Content-Type: application/json' \
--data-binary '{
"rankingRules": [
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"release_date:desc",
"rank:desc"
],
"distinctAttribute": "movie_id",
"searchableAttributes": [
"title",
"overview",
"genres"
],
"displayedAttributes": [
"title",
"overview",
"genres",
"release_date"
],
"stopWords": [
"the",
"a",
"an"
],
"sortableAttributes": [
"title",
"release_date"
],
"synonyms": {
"wolverine": [
"xmen",
"logan"
],
"logan": ["wolverine"]
},
"typoTolerance": {
"minWordSizeForTypos": {
"oneTypo": 8,
"twoTypos": 10
},
"disableOnAttributes": ["title"]
},
"pagination": {
"maxTotalHits": 5000
},
"faceting": {
"maxValuesPerFacet": 200
},
"searchCutoffMs": 150
}'
```
```javascript JS
client.index('movies').updateSettings({
rankingRules: [
'words',
'typo',
'proximity',
'attribute',
'sort',
'exactness',
'release_date:desc',
'rank:desc'
],
distinctAttribute: 'movie_id',
searchableAttributes: [
'title',
'overview',
'genres'
],
displayedAttributes: [
'title',
'overview',
'genres',
'release_date'
],
stopWords: [
'the',
'a',
'an'
],
sortableAttributes: [
'title',
'release_date'
],
synonyms: {
'wolverine': ['xmen', 'logan'],
'logan': ['wolverine']
},
typoTolerance: {
'minWordSizeForTypos': {
'oneTypo': 8,
'twoTypos': 10
},
'disableOnAttributes': [
'title'
]
},
pagination: {
maxTotalHits: 5000
},
faceting: {
maxValuesPerFacet: 200
},
searchCutoffMs: 150
})
```
```python Python
client.index('movies').update_settings({
'rankingRules': [
'words',
'typo',
'proximity',
'attribute',
'sort',
'exactness',
'release_date:desc',
'rank:desc'
],
'distinctAttribute': 'movie_id',
'searchableAttributes': [
'title',
'overview',
'genres'
],
'displayedAttributes': [
'title',
'overview',
'genres',
'release_date'
],
'sortableAttributes': [
'title',
'release_date'
],
'stopWords': [
'the',
'a',
'an'
],
'synonyms': {
'wolverine': ['xmen', 'logan'],
'logan': ['wolverine']
},
'typoTolerance': {
'minWordSizeForTypos': {
'oneTypo': 8,
'twoTypos': 10
},
'disableOnAttributes': ['title']
},
'pagination': {
'maxTotalHits': 5000
},
'faceting': {
'maxValuesPerFacet': 200
},
'searchCutoffMs': 150
})
```
```php PHP
$client->index('movies')->updateSettings([
'rankingRules' => [
'words',
'typo',
'proximity',
'attribute',
'sort',
'exactness',
'release_date:desc',
'rank:desc'
],
'distinctAttribute' => 'movie_id',
'searchableAttributes' => [
'title',
'overview',
'genres'
],
'displayedAttributes' => [
'title',
'overview',
'genres',
'release_date'
],
'stopWords' => [
'the',
'a',
'an'
],
'sortableAttributes' => [
'title',
'release_date'
],
'synonyms' => [
'wolverine' => ['xmen', 'logan'],
'logan' => ['wolverine']
],
'typoTolerance' => [
'minWordSizeForTypos' => [
'oneTypo' => 8,
'twoTypos' => 10
],
'disableOnAttributes' => ['title']
],
'pagination' => [
'maxTotalHits' => 5000
],
'faceting' => [
'maxValuesPerFacet' => 200
],
'searchCutoffMs' => 150
]);
```
```java Java
Settings settings = new Settings();
settings.setRankingRules(
new String[] {
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"release_date:desc",
"rank:desc"
});
settings.setDistinctAttribute("movie_id");
settings.setSearchableAttributes(
new String[] {
"title",
"overview",
"genres"
});
settings.setDisplayedAttributes(
new String[] {
"title",
"overview",
"genres",
"release_date"
});
settings.setStopWords(
new String[] {
"the",
"a",
"an"
});
settings.setSortableAttributes(
new String[] {
"title",
"release_date"
});
HashMap synonyms = new HashMap();
synonyms.put("wolverine", new String[] {"xmen", "logan"});
synonyms.put("logan", new String[] {"wolverine"});
settings.setSynonyms(synonyms);
HashMap minWordSizeTypos =
new HashMap() {
{
put("oneTypo", 8);
put("twoTypos", 10);
}
};
TypoTolerance typoTolerance = new TypoTolerance();
typoTolerance.setMinWordSizeForTypos(minWordSizeTypos);
settings.setTypoTolerance(typoTolerance);
settings.setSearchCutoffMs(150);
client.index("movies").updateSettings(settings);
```
```ruby Ruby
client.index('movies').update_settings({
ranking_rules: [
'words',
'typo',
'proximity',
'attribute',
'sort',
'exactness',
'release_date:desc',
'rank:desc'
],
distinct_attribute: 'movie_id',
searchable_attributes: [
'title',
'overview',
'genres'
],
displayed_attributes: [
'title',
'overview',
'genres',
'release_date'
],
stop_words: [
'the',
'a',
'an'
],
sortable_attributes: [
'title',
'release_date'
],
synonyms: {
wolverine: ['xmen', 'logan'],
logan: ['wolverine']
},
pagination: {
max_total_hits: 5000
},
faceting: {
max_values_per_facet: 200
},
search_cutoff_ms: 150
})
```
```go Go
distinctAttribute := "movie_id"
settings := meilisearch.Settings{
RankingRules: []string{
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"release_date:desc",
"rank:desc",
},
DistinctAttribute: &distinctAttribute,
SearchableAttributes: []string{
"title",
"overview",
"genres",
},
DisplayedAttributes: []string{
"title",
"overview",
"genres",
"release_date",
},
StopWords: []string{
"the",
"a",
"an",
},
SortableAttributes: []string{
"title",
"release_date",
},
Synonyms: map[string][]string{
"wolverine": []string{"xmen", "logan"},
"logan": []string{"wolverine"},
},
TypoTolerance: &meilisearch.TypoTolerance{
MinWordSizeForTypos: meilisearch.MinWordSizeForTypos{
OneTypo: 8,
TwoTypos: 10,
},
DisableOnAttributes: []string{"title"},
},
Pagination: &meilisearch.Pagination{
MaxTotalHits: 5000,
},
Faceting: &meilisearch.Faceting{
MaxValuesPerFacet: 200,
},
SearchCutoffMs: 150,
}
client.Index("movies").UpdateSettings(&settings)
```
```csharp C#
Settings newSettings = new Settings
{
RankingRules = new string[]
{
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"release_date:desc",
"rank:desc"
},
DistinctAttribute = "movie_id",
SearchableAttributes = new string[] { "title", "overview", "genres" },
DisplayedAttributes = new string[] { "title", "overview", "genres", "release_date" },
SortableAttributes = new string[] { "title", "release_date" },
StopWords = new string[] { "the", "a", "an" },
Synonyms = new Dictionary>
{
{ "wolverine", new string[] { "xmen", "logan" } },
{ "logan", new string[] { "wolverine" } },
},
FilterableAttributes = new string[] { },
TypoTolerance = new TypoTolerance
{
DisableOnAttributes = new string[] { "title" },
MinWordSizeForTypos = new TypoTolerance.TypoSize
{
OneTypo = 8,
TwoTypos = 10
}
},
SearchCutoffMs = 150
};
TaskInfo task = await client.Index("movies").UpdateSettingsAsync(newFilters);
```
```rust Rust
let mut synonyms = std::collections::HashMap::new();
synonyms.insert(String::from("wolverine"), vec!["xmen", "logan"]);
synonyms.insert(String::from("logan"), vec!["wolverine"]);
let min_word_size_for_typos = MinWordSizeForTypos {
one_typo: Some(4),
two_typos; Some(12)
}
let typo_tolerance = TypoToleranceSettings {
enabled: Some(true),
disable_on_attributes: Some(vec!["title".to_string()]),
disable_on_words: Some(vec![])
min_word_size_for_typos: Some(min_word_size_for_typos),
};
let settings = Settings::new()
.with_ranking_rules([
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"release_date:desc",
"rank:desc"
])
.with_distinct_attribute(Some("movie_id"))
.with_searchable_attributes([
"title",
"overview",
"genres"
])
.with_displayed_attributes([
"title",
"overview",
"genres",
"release_date"
])
.with_stop_words([
"the",
"a",
"an"
])
.with_sortable_attributes([
"title",
"release_date"
])
.with_synonyms(synonyms)
.with_typo_tolerance(typo_tolerance)
.with_search_cutoff(150);
let task: TaskInfo = client
.index("movies")
.set_settings(&settings)
.await
.unwrap();
```
```swift Swift
let settings = Setting(rankingRules: [
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"release_date:desc",
"rank:desc"
], searchableAttributes: [
"title",
"overview",
"genres"
], displayedAttributes: [
"title",
"overview",
"genres",
"release_date"
], stopWords: [
"the",
"a",
"an"
], synonyms: [
"wolverine": ["xmen", "logan"],
"logan": ["wolverine"]
], distinctAttribute: "movie_id",
sortableAttributes: [
"title",
"release_date"
])
client.index("movies").updateSettings(settings) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').updateSettings(
IndexSettings(
rankingRules: [
'words',
'typo',
'proximity',
'attribute',
'sort',
'exactness',
'release_date:desc',
'rank:desc'
],
distinctAttribute: 'movie_id',
searchableAttributes: ['title', 'overview', 'genres'],
displayedAttributes: [
'title',
'overview',
'genres',
'release_date'
],
stopWords: ['the', 'a', 'an'],
sortableAttributes: ['title', 'release_date'],
synonyms: {
'wolverine': ['xmen', 'logan'],
'logan': ['wolverine'],
},
),
);
```
If Meilisearch encounters an error when updating any of the settings in a request, it immediately stops processing the request and returns an error message. In this case, the database settings remain unchanged. The returned error message will only address the first error encountered.
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset settings
Reset all the settings of an index to their [default value](#settings-object).
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/movies/settings'
```
```javascript JS
client.index('movies').resetSettings()
```
```python Python
client.index('movies').reset_settings()
```
```php PHP
$client->index('movies')->resetSettings();
```
```java Java
client.index("movies").resetSettings();
```
```ruby Ruby
client.index('movies').reset_settings
```
```go Go
client.Index("movies").ResetSettings()
```
```csharp C#
await client.Index("movies").ResetSettingsAsync();
```
```rust Rust
let task: TaskInfo = client
.index("movies")
.reset_settings()
.await
.unwrap();
```
```swift Swift
client.index("movies").resetSettings { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').resetSettings();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Dictionary
Allows users to instruct Meilisearch to consider groups of strings as a single term by adding a supplementary dictionary of user-defined terms.
This is particularly useful when working with datasets containing many domain-specific words, and in languages where words are not separated by whitespace such as Japanese.
Custom dictionaries are also useful in a few use-cases for space-separated languages, such as datasets with names such as `"J. R. R. Tolkien"` and `"W. E. B. Du Bois"`.
User-defined dictionaries can be used together with synonyms. It can be useful to configure Meilisearch so different spellings of an author's initials return the same results:
```json
"dictionary": ["W. E. B.", "W.E.B."],
"synonyms": {
"W. E. B.": ["W.E.B."],
"W.E.B.": ["W. E. B."]
}
```
### Get dictionary
Get an index's user-defined dictionary.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/books/settings/dictionary'
```
```javascript JS
client.index('books').getDictionary()
```
```python Python
client.index('books').get_dictionary()
```
```php PHP
$client->index('books')->getDictionary();
```
```java Java
client.index("books").getDictionarySettings();
```
```ruby Ruby
client.index('books').dictionary
```
```go Go
client.Index("books").GetDictionary()
```
```csharp C#
var indexDictionary = await client.Index("books").GetDictionaryAsync();
```
```rust Rust
let task: TaskInfo = client
.index('books')
.get_dictionary()
.await
.unwrap();
```
```swift Swift
client.index("books").getDictionary { result in
// handle result
}
```
##### Response: `200 OK`
```json
[]
```
### Update dictionary
Update an index's user-defined dictionary.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```json
["J. R. R.", "W. E. B."]
```
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/books/settings/dictionary' \
-H 'Content-Type: application/json' \
--data-binary '[
"J. R. R.",
"W. E. B."
]'
```
```javascript JS
client.index('books').updateDictionary(['J. R. R.', 'W. E. B.'])
```
```python Python
client.index('books').update_dictionary(["J. R. R.", "W. E. B."])
```
```php PHP
$client->index('books')->updateDictionary(['J. R. R.', 'W. E. B.']);
```
```java Java
client.index("books").updateDictionarySettings(new String[] {"J. R. R.", "W. E. B."});
```
```ruby Ruby
client.index('books').update_dictionary(['J. R. R.', 'W. E. B.'])
```
```go Go
client.Index("books").UpdateDictionary([]string{
"J. R. R.",
"W. E. B.",
})
```
```csharp C#
var newDictionary = new string[] { "J. R. R.", "W. E. B." };
await client.Index("books").UpdateDictionaryAsync(newDictionary);
```
```rust Rust
let task: TaskInfo = client
.index('books')
.set_dictionary(['J. R. R.', 'W. E. B.'])
.await
.unwrap();
```
```swift Swift
client.index("books").updateDictionary(["J. R. R.", "W. E. B."]) { result in
// handle result
}
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "books",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2023-09-11T15:39:06.073314Z"
}
```
Use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset dictionary
Reset an index's dictionary to its default value, `[]`.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/books/settings/dictionary'
```
```javascript JS
client.index('books').resetDictionary()
```
```python Python
client.index('books').reset_dictionary()
```
```php PHP
$client->index('books')->resetDictionary();
```
```java Java
client.index("books").resetDictionarySettings();
```
```ruby Ruby
client.index('books').reset_dictionary
```
```go Go
client.Index("books").ResetDictionary()
```
```csharp C#
await client.Index("books").ResetDictionaryAsync();
```
```rust Rust
let task: TaskInfo = client
.index('books')
.reset_dictionary()
.await
.unwrap();
```
```swift Swift
client.index("books").resetDictionary { result in
// handle result
}
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "books",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2022-04-14T20:53:32.863107Z"
}
```
Use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Displayed attributes
The attributes added to the `displayedAttributes` list appear in search results. `displayedAttributes` only affects the search endpoints. It has no impact on the [get documents with POST](/reference/api/documents#get-documents-with-post) and [get documents with GET](/reference/api/documents#get-documents-with-get) endpoints.
By default, the `displayedAttributes` array is equal to all fields in your dataset. This behavior is represented by the value `["*"]`.
[To learn more about displayed attributes, refer to our dedicated guide.](/learn/relevancy/displayed_searchable_attributes#displayed-fields)
### Get displayed attributes
Get the displayed attributes of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/movies/settings/displayed-attributes'
```
```javascript JS
client.index('movies').getDisplayedAttributes()
```
```python Python
client.index('movies').get_displayed_attributes()
```
```php PHP
$client->index('movies')->getDisplayedAttributes();
```
```java Java
client.index("movies").getDisplayedAttributesSettings();
```
```ruby Ruby
client.index('movies').get_displayed_attributes
```
```go Go
client.Index("movies").GetDisplayedAttributes()
```
```csharp C#
await client.Index("movies").GetDisplayedAttributesAsync();
```
```rust Rust
let displayed_attributes: Vec = client
.index("movies")
.get_displayed_attributes()
.await
.unwrap();
```
```swift Swift
client.index("movies").getDisplayedAttributes { (result) in
switch result {
case .success(let displayedAttributes):
print(displayedAttributes)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').getDisplayedAttributes();
```
##### Response: `200 Ok`
```json
[
"title",
"overview",
"genres",
"release_date.year"
]
```
### Update displayed attributes
Update the displayed attributes of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
[, , …]
```
An array of strings. Each string should be an attribute that exists in the selected index.
If an attribute contains an object, you can use dot notation to specify one or more of its keys, for example, `"displayedAttributes": ["release_date.year"]`.
If the field does not exist, no error will be thrown.
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/movies/settings/displayed-attributes' \
-H 'Content-Type: application/json' \
--data-binary '[
"title",
"overview",
"genres",
"release_date"
]'
```
```javascript JS
client.index('movies').updateDisplayedAttributes([
'title',
'overview',
'genres',
'release_date'
])
```
```python Python
client.index('movies').update_displayed_attributes([
'title',
'overview',
'genres',
'release_date'
])
```
```php PHP
$client->index('movies')->updateDisplayedAttributes([
'title',
'overview',
'genres',
'release_date'
]);
```
```java Java
client.index("movies").updateDisplayedAttributesSettings(new String[]
{
"title",
"overview",
"genres",
"release_date"
});
```
```ruby Ruby
client.index('movies').update_displayed_attributes([
'title',
'overview',
'genres',
'release_date'
])
```
```go Go
displayedAttributes := []string{
"title",
"overview",
"genres",
"release_date",
}
client.Index("movies").UpdateDisplayedAttributes(&displayedAttributes)
```
```csharp C#
await client.Index("movies").UpdateDisplayedAttributesAsync(new[]
{
"title",
"overview",
"genres",
"release_date"
});
```
```rust Rust
let displayed_attributes = [
"title",
"overview",
"genres",
"release_date"
];
let task: TaskInfo = client
.index("movies")
.set_displayed_attributes(&displayed_attributes)
.await
.unwrap();
```
```swift Swift
let displayedAttributes: [String] = ["title", "overview", "genres", "release_date"]
client.index("movies").updateDisplayedAttributes(displayedAttributes) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').updateDisplayedAttributes([
'title',
'overview',
'genres',
'release_date',
]);
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset displayed attributes
Reset the displayed attributes of the index to the default value.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/movies/settings/displayed-attributes'
```
```javascript JS
client.index('movies').resetDisplayedAttributes()
```
```python Python
client.index('movies').reset_displayed_attributes()
```
```php PHP
$client->index('movies')->resetDisplayedAttributes();
```
```java Java
client.index("movies").resetDisplayedAttributesSettings();
```
```ruby Ruby
client.index('movies').reset_displayed_attributes
```
```go Go
client.Index("movies").ResetDisplayedAttributes()
```
```csharp C#
await client.Index("movies").ResetDisplayedAttributesAsync();
```
```rust Rust
let task: TaskInfo = client
.index("movies")
.reset_displayed_attributes()
.await
.unwrap();
```
```swift Swift
client.index("movies").resetDisplayedAttributes { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').resetDisplayedAttributes();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Distinct attribute
The distinct attribute is a field whose value will always be unique in the returned documents.
Updating distinct attributes will re-index all documents in the index, which can take some time. We recommend updating your index settings first and then adding documents as this reduces RAM consumption.
[To learn more about the distinct attribute, refer to our dedicated guide.](/learn/relevancy/distinct_attribute)
### Get distinct attribute
Get the distinct attribute of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/shoes/settings/distinct-attribute'
```
```javascript JS
client.index('shoes').getDistinctAttribute()
```
```python Python
client.index('shoes').get_distinct_attribute()
```
```php PHP
$client->index('shoes')->getDistinctAttribute();
```
```java Java
client.index("shoes").getDistinctAttributeSettings();
```
```ruby Ruby
client.index('shoes').distinct_attribute
```
```go Go
client.Index("shoes").GetDistinctAttribute()
```
```csharp C#
string result = await client.Index("shoes").GetDistinctAttributeAsync();
```
```rust Rust
let distinct_attribute: Option = client
.index("shoes")
.get_distinct_attribute()
.await
.unwrap();
```
```swift Swift
client.index("shoes").getDistinctAttribute { (result) in
switch result {
case .success(let distinctAttribute):
print(distinctAttribute)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('shoes').getDistinctAttribute();
```
##### Response: `200 Ok`
```json
"skuid"
```
### Update distinct attribute
Update the distinct attribute field of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
```
A string. The string should be an attribute that exists in the selected index.
If an attribute contains an object, you can use dot notation to set one or more of its keys as a value for this setting, for example, `"distinctAttribute": "product.skuid"`.
If the field does not exist, no error will be thrown.
[To learn more about the distinct attribute, refer to our dedicated guide.](/learn/relevancy/distinct_attribute)
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/shoes/settings/distinct-attribute' \
-H 'Content-Type: application/json' \
--data-binary '"skuid"'
```
```javascript JS
client.index('shoes').updateDistinctAttribute('skuid')
```
```python Python
client.index('shoes').update_distinct_attribute('skuid')
```
```php PHP
$client->index('shoes')->updateDistinctAttribute('skuid');
```
```java Java
client.index("shoes").updateDistinctAttributeSettings("skuid");
```
```ruby Ruby
client.index('shoes').update_distinct_attribute('skuid')
```
```go Go
client.Index("shoes").UpdateDistinctAttribute("skuid")
```
```csharp C#
TaskInfo result = await client.Index("shoes").UpdateDistinctAttributeAsync("skuid");
```
```rust Rust
let task: TaskInfo = client
.index("shoes")
.set_distinct_attribute("skuid")
.await
.unwrap();
```
```swift Swift
client.index("shoes").updateDistinctAttribute("skuid") { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('shoes').updateDistinctAttribute('skuid');
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset distinct attribute
Reset the distinct attribute of an index to its default value.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/shoes/settings/distinct-attribute'
```
```javascript JS
client.index('shoes').resetDistinctAttribute()
```
```python Python
client.index('shoes').reset_distinct_attribute()
```
```php PHP
$client->index('shoes')->resetDistinctAttribute();
```
```java Java
client.index("shoes").resetDistinctAttributeSettings();
```
```ruby Ruby
client.index('shoes').reset_distinct_attribute
```
```go Go
client.Index("shoes").ResetDistinctAttribute()
```
```csharp C#
TaskInfo result = await client.Index("shoes").ResetDistinctAttributeAsync();
```
```rust Rust
let task: TaskInfo = client
.index("shoes")
.reset_distinct_attribute()
.await
.unwrap();
```
```swift Swift
client.index("shoes").resetDistinctAttribute { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('shoes').resetDistinctAttribute();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Faceting
With Meilisearch, you can create [faceted search interfaces](/learn/filtering_and_sorting/search_with_facet_filters). This setting allows you to:
* Define the maximum number of values returned by the `facets` search parameter
* Sort facet values by value count or alphanumeric order
[To learn more about faceting, refer to our dedicated guide.](/learn/filtering_and_sorting/search_with_facet_filters)
### Faceting object
| Name | Type | Default value | Description |
| :---------------------- | :------ | :--------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------- |
| **`maxValuesPerFacet`** | Integer | `100` | Maximum number of facet values returned for each facet. Values are sorted in ascending lexicographical order |
| **`sortFacetValuesBy`** | Object | All facet values are sorted in ascending alphanumeric order (`"*": "alpha"`) | Customize facet order to sort by descending value count (`count`) or ascending alphanumeric order (`alpha`) |
### Get faceting settings
Get the faceting settings of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/books/settings/faceting'
```
```javascript JS
client.index('books').getFaceting()
```
```python Python
client.index('books').get_faceting_settings()
```
```php PHP
$client->index('books')->getFaceting();
```
```java Java
client.index("books").getFacetingSettings();
```
```ruby Ruby
client.index('books').faceting
```
```go Go
client.Index("books").GetFaceting()
```
```csharp C#
await client.Index("movies").GetFacetingAsync();
```
```rust Rust
let faceting: FacetingSettings = client
.index("books")
.get_faceting()
.await
.unwrap();
```
```dart Dart
await client.index('movies').getFaceting();
```
##### Response: `200 OK`
```json
{
"maxValuesPerFacet": 100,
"sortFacetValuesBy": {
"*": "alpha"
}
}
```
### Update faceting settings
Partially update the faceting settings for an index. Any parameters not provided in the body will be left unchanged.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
{
"maxValuesPerFacet": ,
"sortFacetValuesBy": {
: "count",
: "alpha"
}
}
```
| Name | Type | Default value | Description |
| :---------------------- | :------ | :--------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------- |
| **`maxValuesPerFacet`** | Integer | `100` | Maximum number of facet values returned for each facet. Values are sorted in ascending lexicographical order |
| **`sortFacetValuesBy`** | Object | All facet values are sorted in ascending alphanumeric order (`"*": "alpha"`) | Customize facet order to sort by descending value count(`count`) or ascending alphanumeric order (`alpha`) |
Suppose a query's search results contain a total of three values for a `colors` facet: `blue`, `green`, and `red`. If you set `maxValuesPerFacet` to `2`, Meilisearch will only return `blue` and `green` in the response body's `facetDistribution` object.
Setting `maxValuesPerFacet` to a high value might negatively impact performance.
#### Example
The following code sample sets `maxValuesPerFacet` to `2`, sorts the `genres` facet by descending count, and all other facets in ascending alphanumeric order:
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/indexes/books/settings/faceting' \
-H 'Content-Type: application/json' \
--data-binary '{
"maxValuesPerFacet": 2,
"sortFacetValuesBy": {
"*": "alpha",
"genres": "count"
}
}'
```
```javascript JS
client.index('books').updateFaceting({
maxValuesPerFacet: 2
sortFacetValuesBy: {
'*': 'alpha',
genres: 'count'
}
})
```
```python Python
params = {
'maxValuesPerFacet': 2,
'sortFacetValuesBy': {
'*': 'count',
'genres': 'count'
}
}
client.index('books').update_faceting_settings(params)
```
```php PHP
$client->index('books')->updateFaceting([
'maxValuesPerFacet' => 2,
'sortFacetValuesBy' => ['*' => 'alpha', 'genres' => 'count']
]);
```
```java Java
Faceting newFaceting = new Faceting();
newFaceting.setMaxValuesPerFacet(2);
HashMap facetSortValues = new HashMap<>();
facetSortValues.put("*", FacetSortValue.ALPHA);
facetSortValues.put("genres", FacetSortValue.COUNT);
newFaceting.setSortFacetValuesBy(facetSortValues);
client.index("books").updateFacetingSettings(newFaceting);
```
```ruby Ruby
client.index('books').update_faceting({
max_values_per_facet: 2,
sort_facet_values_by: {
'*': 'alpha',
genres: 'count'
}
})
```
```go Go
client.Index("books").UpdateFaceting(&meilisearch.Faceting{
MaxValuesPerFacet: 2,
SortFacetValuesBy: {
"*": SortFacetTypeAlpha,
"genres": SortFacetTypeCount,
}
})
```
```csharp C#
var faceting = new Faceting
{
MaxValuesPerFacet = 2,
SortFacetValuesBy = new Dictionary
{
["*"] = SortFacetValuesByType.Alpha,
["genres"] = SortFacetValuesByType.Count
}
};
await client.Index("books").UpdateFacetingAsync(faceting);
```
```rust Rust
let mut facet_sort_setting = BTreeMap::new();
facet_sort_setting.insert(String::from("*"), FacetSortValue::Alpha);
facet_sort_setting.insert(String::from("genres"), FacetSortValue::Count);
let mut faceting = FacetingSettings {
max_values_per_facet: 2,
sort_facet_values_by: Some(facet_sort_setting),
};
let task: TaskInfo = client
.index("books")
.set_faceting(&faceting)
.await
.unwrap();
```
```dart Dart
await client.index('books').updateFaceting(Faceting(
maxValuesPerFacet: 2,
sortFacetValuesBy: {
'*': FacetingSortTypes.alpha,
'genres': FacetingSortTypes.count
}));
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "books",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2022-04-14T20:56:44.991039Z"
}
```
You can use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset faceting settings
Reset an index's faceting settings to their [default value](#faceting-object). Setting `sortFacetValuesBy` to `null`(`--data-binary '{ "sortFacetValuesBy": null }'`), will restore it to the default value (`"*": "alpha"`).
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/books/settings/faceting'
```
```javascript JS
client.index('books').resetFaceting()
```
```python Python
client.index('books').reset_faceting_settings()
```
```php PHP
$client->index('books')->resetFaceting();
```
```java Java
client.index("books").resetFacetingSettings();
```
```ruby Ruby
index('books').reset_faceting
```
```go Go
client.Index("books").ResetFaceting()
```
```csharp C#
await client.Index("movies").ResetFacetingAsync();
```
```rust Rust
let task: TaskInfo = client
.index("books")
.reset_faceting()
.await
.unwrap();
```
```dart Dart
await client.index('movies').resetFaceting();
```
##### Response: `200 OK`
```json
{
"taskUid": 1,
"indexUid": "books",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2022-04-14T20:53:32.863107Z"
}
```
You can use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Filterable attributes
Attributes in the `filterableAttributes` list can be used as [filters](/learn/filtering_and_sorting/filter_search_results) or [facets](/learn/filtering_and_sorting/search_with_facet_filters).
Updating filterable attributes will re-index all documents in the index, which can take some time. To reduce RAM consumption, first update your index settings and then add documents.
### Filterable attribute object
`filterableAttributes` may be an array of either strings or filterable attribute objects.
Filterable attribute objects must contain the following fields:
| Name | Type | Default value | Description |
| ----------------------- | ---------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| **`attributePatterns`** | Array of strings | `[]` | A list of strings indicating filterable fields |
| **`features`** | Object | `{"facetSearch": false, "filters": {"equality": true, "comparison": false}` | A list outlining filter types enabled for the specified attributes |
#### `attributePatterns`
Attribute patterns may begin or end with a \* wildcard to match multiple fields: `customer_*`, `attribute*`.
#### `features`
`features` allows you to decide which filter features are allowed for the specified attributes. It accepts the following fields:
* `facetSearch`: Whether facet search should be enabled for the specified attributes. Boolean, defaults to `false`
* `filter`: A list outlining the filter types for the specified attributes. Must be an object and accepts the following fields:
* `equality`: Enables `=`, `!=`, `IN`, `EXISTS`, `IS NULL`, `IS EMPTY`, `NOT`, `AND`, and `OR`. Boolean, defaults to `true`
* `comparison`: Enables `>`, `>=`, `<`, `<=`, `TO`, `EXISTS`, `IS NULL`, `IS EMPTY`, `NOT`, `AND`, and `OR`. Boolean, defaults to `false`
Calculating `comparison` filters is a resource-intensive operation. Disabling them may lead to better search and indexing performance. `equality` filters use fewer resources and have limited impact on performance.
#### Filterable attributes and reserved attributes
Use the simple string syntax to match reserved attributes. Reserved Meilisearch fields are always prefixed with an underscore (`_`), such as `_geo` and `_vector`.
If set as a filterable attribute, reserved attributes ignore the `features` field and automatically activate all search features. Reserved fields will not be matched by wildcard `attributePatterns` such as `_*`.
### Get filterable attributes
Get the filterable attributes for an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/movies/settings/filterable-attributes'
```
```javascript JS
client.index('movies').getFilterableAttributes()
```
```python Python
client.index('movies').get_filterable_attributes()
```
```php PHP
$client->index('movies')->getFilterableAttributes();
```
```java Java
client.index("movies").getFilterableAttributesSettings();
```
```ruby Ruby
client.index('movies').filterable_attributes
```
```go Go
client.Index("movies").GetFilterableAttributes()
```
```csharp C#
IEnumerable attributes = await client.Index("movies").GetFilterableAttributesAsync();
```
```rust Rust
let filterable_attributes: Vec = client
.index("movies")
.get_filterable_attributes()
.await
.unwrap();
```
```swift Swift
client.index("movies").getFilterableAttributes { (result) in
switch result {
case .success(let attributes):
print(attributes)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').getFilterableAttributes();
```
##### Response: `200 Ok`
```json
[
"genres",
"director",
"release_date.year"
]
```
### Update filterable attributes
Update an index's filterable attributes list.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
[, , …]
```
An array of strings containing the attributes that can be used as filters at query time. All filter types are enabled for the specified attributes when using the array of strings format.
You may also use an array of objects:
```
[
{
"attributePatterns": [, , …],
"features": {
"facetSearch": ,
"filter": {
"equality": ,
"comparison":
}
}
}
]
```
If the specified field does not exist, Meilisearch will silently ignore it.
If an attribute contains an object, you can use dot notation to set one or more of its keys as a value for this setting: `"filterableAttributes": ["release_date.year"]` or `"attributePatterns": ["release_date.year"]`.
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/movies/settings/filterable-attributes' \
-H 'Content-Type: application/json' \
--data-binary '[
"genres",
"director",
{
"attributePatterns": ["*_ratings"],
"features": {
"facetSearch": false,
"filter": {
"equality": true,
"comparison": false
}
}
}
]'
```
```javascript JS
client.index('movies')
.updateFilterableAttributes([
"genres",
{
attributePatterns: ["genre"],
features: {
facetSearch: true,
filter: { equality: true, comparison: false },
},
}
])
```
```python Python
client.index('movies').update_filterable_attributes([
'genres',
'director'
])
```
```php PHP
$client->index('movies')->updateFilterableAttributes([
'author',
[
'attributePatterns' => ['genres'],
'features' => [
'facetSearch' => true,
'filter' => [
'equality' => true,
'comparison' => false,
],
],
],
]);
```
```java Java
Settings settings = new Settings();
settings.setFilterableAttributes(new String[] {"genres", "director"});
client.index("movies").updateSettings(settings);
```
```ruby Ruby
client.index('movies').update_filterable_attributes([
'genres',
'director'
])
```
```go Go
filterableAttributes := []interface{}{
"genres",
"director",
AttributeRule{
AttributePatterns: []string{"tag"}
Features: AttributeFeatures{
FacetSearch: false,
Filter: FilterFeatures{
Equality: true,
Comparison: false,
}
}
},
map[string]interface{}{
"attributePatterns": []interface{}{"year"}
"features": map[string]interface{}{
"facetSearch": false,
"filter": map[string]interface{}{
"equality": true,
"comparison": true,
}
}
}
}
client.Index("movies").UpdateFilterableAttributes(&filterableAttributes)
```
```csharp C#
List attributes = new() { "genres", "director" };
TaskInfo result = await client.Index("movies").UpdateFilterableAttributesAsync(attributes);
```
```rust Rust
let filterable_attributes = [
"genres",
"director"
];
let task: TaskInfo = client
.index("movies")
.set_filterable_attributes(&filterable_attributes)
.await
.unwrap();
```
```swift Swift
client.index("movies").updateFilterableAttributes(["genre", "director"]) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client
.index('movies')
.updateFilterableAttributes(['genres', 'director']);
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset filterable attributes
Reset an index's filterable attributes list back to its default value.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/movies/settings/filterable-attributes'
```
```javascript JS
client.index('movies').resetFilterableAttributes()
```
```python Python
client.index('movies').reset_filterable_attributes()
```
```php PHP
$client->index('movies')->resetFilterableAttributes();
```
```java Java
client.index("movies").resetFilterableAttributesSettings();
```
```ruby Ruby
client.index('movies').reset_filterable_attributes
```
```go Go
client.Index("movies").ResetFilterableAttributes()
```
```csharp C#
TaskInfo result = await client.Index("movies").ResetFilterableAttributesAsync();
```
```rust Rust
let task: TaskInfo = client
.index("movies")
.reset_filterable_attributes()
.await
.unwrap();
```
```swift Swift
client.index("movies").resetFilterableAttributes { (result) in
switch result {
case .success(let attributes):
print(attributes)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').resetFilterableAttributes();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Localized attributes
By default, Meilisearch auto-detects the languages used in your documents. This setting allows you to explicitly define which languages are present in a dataset, and in which fields.
Localized attributes affect `searchableAttributes`, `filterableAttributes`, and `sortableAttributes`.
Configuring multiple languages for a single index may negatively impact performance.
[`locales`](/reference/api/search#query-locales) and `localizedAttributes` have the same goal: explicitly state the language used in a search when Meilisearch's language auto-detection is not working as expected.
If you believe Meilisearch is detecting incorrect languages because of the query text, explicitly set the search language with `locales`.
If you believe Meilisearch is detecting incorrect languages because of document, explicitly set the document language at the index level with `localizedAttributes`.
For full control over the way Meilisearch detects languages during indexing and at search time, set both `locales` and `localizedAttributes`.
### Localized attributes object
`localizedAttributes` must be an array of locale objects. Its default value is `[]`.
Locale objects must have the following fields:
| Name | Type | Default value | Description |
| :---------------------- | :--------------- | :------------ | :---------------------------------------------------------------------------- |
| **`locales`** | Array of strings | `[]` | A list of strings indicating one or more ISO-639 locales |
| **`attributePatterns`** | Array of strings | `[]` | A list of strings indicating which fields correspond to the specified locales |
#### `locales`
Meilisearch supports the following [ISO-639-3](https://iso639-3.sil.org/) three-letter `locales`: `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`.
You may alternatively use [ISO-639-1 two-letter equivalents](https://iso639-3.sil.org/code_tables/639/data) to the supported `locales`.
You may also assign an empty array to `locales`. In this case, Meilisearch will auto-detect the language of the associated `attributePatterns`.
#### `attributePatterns`
Attribute patterns may begin or end with a `*` wildcard to match multiple fields: `en_*`, `*-ar`.
You may also set `attributePatterns` to `*`, in which case Meilisearch will treat all fields as if they were in the associated locale.
### Get localized attributes settings
Get the localized attributes settings of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/localized-attributes'
```
```javascript JS
client.index('INDEX_NAME').getLocalizedAttributes()
```
```python Python
client.index('INDEX_NAME').get_localized_attributes()
```
```php PHP
$client->index('INDEX_NAME')->getLocalizedAttributes();
```
```java Java
client.index("INDEX_NAME").getLocalizedAttributesSettings();
```
```ruby Ruby
client.index('INDEX_NAME').localized_attributes
```
```go Go
client.index("INDEX_NAME").GetLocalizedAttributes()
```
```rust Rust
let localized_attributes: Option> = client
.index("books")
.get_localized_attributes()
.await
.unwrap();
```
##### Response: `200 OK`
```json
[
{"locales": ["jpn"], "attributePatterns": ["*_ja"]}
]
```
### Update localized attribute settings
Update the localized attributes settings of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
[
{
"locales": [, …],
"attributePatterns": [, …],
}
]
```
| Name | Type | Default value | Description |
| :------------------------ | :--------------- | :------------ | :--------------------------------------------------------- |
| **`localizedAttributes`** | Array of objects | `[]` | Explicitly set specific locales for one or more attributes |
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/localized-attributes' \
-H 'Content-Type: application/json' \
--data-binary '[
{"locales": ["jpn"], "attributePatterns": ["*_ja"]}
]'
```
```javascript JS
client.index('INDEX_NAME').updateLocalizedAttributes([
{ attributePatterns: ['*_ja'], locales: ['jpn'] },
])
```
```python Python
client.index('INDEX_NAME').update_localized_attributes([
{'attribute_patterns': ['*_ja'], 'locales': ['jpn']}
])
```
```php PHP
$client->index('INDEX_NAME')->updateLocalizedAttributes([
'locales' => ['jpn'],
'attributePatterns' => ['*_ja']
]);
```
```java Java
LocalizedAttribute attribute = new LocalizedAttribute();
attribute.setAttributePatterns(new String[] {"jpn"});
attribute.setLocales(new String[] {"*_ja"});
client.index("INDEX_NAME").updateLocalizedAttributesSettings(
new LocalizedAttributes[] {attribute}
);
```
```ruby Ruby
client.index('INDEX_NAME').update_localized_attributes([
{ attribute_patterns: ['*_ja'], locales: ['jpn'] },
])
```
```go Go
client.index("INDEX_NAME").UpdateLocalizedAttributes([]*LocalizedAttributes{
{ AttributePatterns: ["*_ja"], Locales: ["jpn"] },
})
```
```rust Rust
let localized_attributes = vec![LocalizedAttributes {
locales: vec!["jpn".to_string()],
attribute_patterns: vec!["*_ja".to_string()],
}];
let task: TaskInfo = client
.index("books")
.set_localized_attributes(&localizced_attributes)
.await
.unwrap();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "INDEX_NAME",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2022-04-14T20:56:44.991039Z"
}
```
You can use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset localized attributes settings
Reset an index's localized attributes to their [default value](#localized-attributes-object).
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/localized-attributes'
```
```javascript JS
client.index('INDEX_NAME').resetLocalizedAttributes()
```
```python Python
client.index('INDEX_NAME').reset_localized_attributes()
```
```php PHP
$client->index('INDEX_NAME')->resetLocalizedAttributes();
```
```java Java
client.index("INDEX_NAME").resetLocalizedAttributesSettings();
```
```ruby Ruby
client.index('INDEX_NAME').reset_localized_attributes
```
```go Go
client.index("INDEX_NAME").ResetLocalizedAttributes()
```
```rust Rust
let task: TaskInfo = client
.index("books")
.reset_localized_attributes()
.await
.unwrap();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "INDEX_NAME",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2022-04-14T20:53:32.863107Z"
}
```
You can use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Pagination
To protect your database from malicious scraping, Meilisearch has a default limit of 1000 results per search. This setting allows you to configure the maximum number of results returned per search.
`maxTotalHits` takes priority over search parameters such as `limit`, `offset`, `hitsPerPage`, and `page`.
For example, if you set `maxTotalHits` to 100, you will not be able to access search results beyond 100 no matter the value configured for `offset`.
[To learn more about paginating search results with Meilisearch, refer to our dedicated guide.](/guides/front_end/pagination)
### Pagination object
| Name | Type | Default value | Description |
| :----------------- | :------ | :------------ | :---------------------------------------------------------- |
| **`maxTotalHits`** | Integer | `1000` | The maximum number of search results Meilisearch can return |
Setting `maxTotalHits` to a value higher than the default will negatively impact search performance. Setting `maxTotalHits` to values over `20000` may result in queries taking seconds to complete.
### Get pagination settings
Get the pagination settings of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/books/settings/pagination'
```
```javascript JS
client.index('books').getPagination()
```
```python Python
client.index('books').get_pagination_settings()
```
```php PHP
$client->index('books')->getPagination();
```
```java Java
client.index("books").getPaginationSettings();
```
```ruby Ruby
index('books').pagination
```
```go Go
client.Index("books").GetPagination()
```
```csharp C#
await client.Index("movies").GetPaginationAsync();
```
```rust Rust
let pagination: PaginationSetting = client
.index("books")
.get_pagination()
.await
.unwrap();
```
```dart Dart
await client.index('movies').getPagination();
```
##### Response: `200 OK`
```json
{
"maxTotalHits": 1000
}
```
### Update pagination settings
Partially update the pagination settings for an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
{maxTotalHits: }
```
| Name | Type | Default value | Description |
| :----------------- | :------ | :------------ | :---------------------------------------------------------- |
| **`maxTotalHits`** | Integer | `1000` | The maximum number of search results Meilisearch can return |
Setting `maxTotalHits` to a value higher than the default will negatively impact search performance. Setting `maxTotalHits` to values over `20000` may result in queries taking seconds to complete.
#### Example
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/indexes/books/settings/pagination' \
-H 'Content-Type: application/json' \
--data-binary '{
"maxTotalHits": 100
}'
```
```javascript JS
client.index('books').updateSettings({ pagination: { maxTotalHits: 100 }})
```
```python Python
client.index('books').update_pagination_settings({'maxTotalHits': 100})
```
```php PHP
$client->index('books')->updateSettings([
'pagination' => [
'maxTotalHits' => 100
]
]);
```
```java Java
Pagination newPagination = new Pagination();
newPagination.setMaxTotalHits(100);
client.index("books").updatePaginationSettings(newPagination);
```
```ruby Ruby
index('books').update_pagination({ max_total_hits: 100 })
```
```go Go
client.Index("books").UpdatePagination(&meilisearch.Pagination{
MaxTotalHits: 100,
})
```
```csharp C#
var pagination = new Pagination {
MaxTotalHits = 20
};
await client.Index("movies").UpdatePaginationAsync(pagination);
```
```rust Rust
let pagination = PaginationSetting {max_total_hits:100};
let task: TaskInfo = client
.index("books")
.set_pagination(pagination)
.await
.unwrap();
```
```dart Dart
await client
.index('books')
.updatePagination(Pagination(maxTotalHits: 100));
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "books",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2022-04-14T20:56:44.991039Z"
}
```
You can use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset pagination settings
Reset an index's pagination settings to their [default value](#pagination-object).
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/books/settings/pagination'
```
```javascript JS
client.index('books').resetPagination()
```
```python Python
client.index('books').reset_pagination_settings()
```
```php PHP
$client->index('books')->resetPagination();
```
```java Java
client.index("books").resetPaginationSettings();
```
```ruby Ruby
index('books').reset_pagination
```
```go Go
client.Index("books").ResetPagination()
```
```csharp C#
await client.Index("movies").ResetPaginationAsync();
```
```rust Rust
let task: TaskInfo = client
.index("books")
.reset_pagination()
.await
.unwrap();
```
```dart Dart
await client.index('movies').resetPagination();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "books",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2022-04-14T20:53:32.863107Z"
}
```
You can use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Proximity precision
Calculating the distance between words is a resource-intensive operation. Lowering the precision of this operation may significantly improve performance and will have little impact on result relevancy in most use-cases. Meilisearch uses word distance when [ranking results according to proximity](/learn/relevancy/ranking_rules#3-proximity) and when users perform [phrase searches](/reference/api/search#phrase-search).
`proximityPrecision` accepts one of the following string values:
* `"byWord"`: calculate the precise distance between query terms. Higher precision, but may lead to longer indexing time. This is the default setting
* `"byAttribute"`: determine if multiple query terms are present in the same attribute. Lower precision, but shorter indexing time
### Get proximity precision settings
Get the proximity precision settings of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/books/settings/proximity-precision'
```
```javascript JS
client.index('books').getProximityPrecision()
```
```python Python
client.index('books').get_proximity_precision()
```
```php PHP
$client->index('books')->getProximityPrecision();
```
```java Java
client.index("books").getProximityPrecisionSettings();
```
```ruby Ruby
client.index('books').proximity_precision
```
```go Go
client.Index("books").GetProximityPrecision()
```
```csharp C#
await client.Index("books").GetProximityPrecisionAsync();
```
```rust Rust
let proximity_precision: String = client
.index("books")
.get_proximity_precision()
.await
.unwrap();
```
```swift Swift
let precisionValue = try await self.client.index("books").getProximityPrecision()
```
##### Response: `200 OK`
```
"byWord"
```
### Update proximity precision settings
Update the pagination settings for an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
"byWord"|"byAttribute"
```
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/books/settings/proximity-precision' \
-H 'Content-Type: application/json' \
--data-binary '"byAttribute"'
```
```javascript JS
client.index('books').updateProximityPrecision('byAttribute')
```
```python Python
client.index('books').update_proximity_precision(ProximityPrecision.BY_ATTRIBUTE)
```
```php PHP
$client->index('books')->updateProximityPrecision('byAttribute');
```
```java Java
client.index("books").updateProximityPrecisionSettings("byAttribute");
```
```ruby Ruby
client.index('books').update_proximity_precision('byAttribute')
```
```go Go
client.Index("books").UpdateProximityPrecision(ByAttribute)
```
```csharp C#
await client.Index("books").UpdateProximityPrecisionAsync("byAttribute");
```
```rust Rust
let task: TaskInfo = client
.index("books")
.set_proximity_precision("byAttribute".to_string())
.await
.unwrap();
```
```swift Swift
let task = try await self.client.index("books").updateProximityPrecision(.byWord)
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "books",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2023-04-14T15:50:29.821044Z"
}
```
You can use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset proximity precision settings
Reset an index's proximity precision setting to its default value.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/books/settings/proximity-precision'
```
```javascript JS
client.index('books').resetProximityPrecision()
```
```python Python
client.index('books').reset_proximity_precision()
```
```php PHP
$client->index('books')->resetProximityPrecision();
```
```java Java
client.index("books").resetProximityPrecisionSettings();
```
```ruby Ruby
client.index('books').reset_proximity_precision
```
```go Go
client.Index("books").ResetProximityPrecision()
```
```csharp C#
await client.Index("books").ResetProximityPrecisionAsync();
```
```rust Rust
let task: TaskInfo = client
.index("books")
.reset_proximity_precision()
.await
.unwrap();
```
```swift Swift
let task = try await self.client.index("books").resetProximityPrecision()
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "books",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2023-04-14T15:51:47.821044Z"
}
```
You can use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Facet search
Processing filterable attributes for facet search is a resource-intensive operation. This feature is enabled by default, but disabling it may speed up indexing.
`facetSearch` accepts a single Boolean value. If set to `false`, it disables facet search for the whole index. Meilisearch returns an error if you try to access the `/facet-search` endpoint when facet search is disabled.
### Get facet search settings
Get the facet search settings of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/INDEX_UID/settings/facet-search'
```
```javascript JS
client.index('INDEX_NAME').getFacetSearch();
```
```python Python
client.index('books').get_facet_search_settings()
```
```php PHP
$client->index('INDEX_NAME')->getFacetSearch();
```
```ruby Ruby
client.index('INDEX_UID').facet_search_setting
```
```go Go
client.Index("books").GetFacetSearch()
```
```rust Rust
let facet_search: bool = client
.index(INDEX_UID)
.get_facet_search()
.await
.unwrap();
```
##### Response: `200 OK`
```json
true
```
### Update facet search settings
Update the facet search settings for an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
```
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/INDEX_UID/settings/facet-search' \
-H 'Content-Type: application/json' \
--data-binary 'false'
```
```javascript JS
client.index('INDEX_NAME').updateFacetSearch(false);
```
```python Python
client.index('books').update_facet_search_settings(False)
```
```php PHP
$client->index('INDEX_NAME')->updateFacetSearch(false);
```
```ruby Ruby
client.index('INDEX_UID').update_facet_search_setting(false)
```
```go Go
client.Index("books").UpdateFacetSearch(false)
```
```rust Rust
let task: TaskInfo = client
.index(INDEX_UID)
.set_facet_search(false)
.await
.unwrap();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "INDEX_UID",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2024-07-19T22:33:18.523881Z"
}
```
Use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset facet search settings
Reset an index's facet search to its default settings.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/INDEX_UID/settings/facet-search'
```
```javascript JS
client.index('INDEX_NAME').resetFacetSearch();
```
```python Python
client.index('books').reset_facet_search_settings()
```
```php PHP
$client->index('INDEX_NAME')->resetFacetSearch();
```
```ruby Ruby
client.index('INDEX_UID').reset_facet_search_setting
```
```go Go
client.Index("books").ResetFacetSearch()
```
```rust Rust
let task: TaskInfo = client
.index(INDEX_UID)
.reset_facet_search()
.await
.unwrap();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "INDEX_UID",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2024-07-19T22:35:33.723983Z"
}
```
Use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Prefix search
Prefix search is the process through which Meilisearch matches documents that begin with a specific query term, instead of only exact matches. This is a resource-intensive operation that happens during indexing by default.
Use `prefixSearch` to change how prefix search works. It accepts one of the following strings:
* `"indexingTime"`: calculate prefix search during indexing. This is the default behavior
* `"disabled"`: do not calculate prefix search. May speed up indexing, but will severely impact search result relevancy
### Get prefix search settings
Get the prefix search settings of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/INDEX_UID/settings/prefix-search'
```
```javascript JS
client.index('INDEX_NAME').getPrefixSearch();
```
```python Python
client.index('books').get_prefix_search()
```
```php PHP
$client->index('INDEX_NAME')->getPrefixSearch();
```
```ruby Ruby
client.index('INDEX_UID').prefix_search
```
```go Go
client.Index("books").GetPrefixSearch()
```
```rust Rust
let prefix_search: PrefixSearchSettings = client
.index(INDEX_UID)
.get_prefix_search()
.await
.unwrap();
```
##### Response: `200 OK`
```json
"indexingTime"
```
### Update prefix search settings
Update the prefix search settings for an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
"indexingTime" | "disabled"
```
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/INDEX_UID/settings/prefix-search' \
-H 'Content-Type: application/json' \
--data-binary '"disabled"'
```
```javascript JS
client.index('INDEX_NAME').updatePrefixSearch('disabled');
```
```python Python
client.index('books').update_prefix_search(PrefixSearch.DISABLED)
```
```php PHP
$client->index('INDEX_NAME')->updatePrefixSearch('disabled');
```
```ruby Ruby
client.index('INDEX_UID').update_prefix_search('disabled')
```
```go Go
client.Index("books").UpdatePrefixSearch("disabled")
```
```rust Rust
let task: TaskInfo = client
.index(INDEX_UID)
.set_prefix_search(PrefixSearchSettings::Disabled)
.await
.unwrap();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "INDEX_UID",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2024-07-19T22:33:18.523881Z"
}
```
Use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset prefix search settings
Reset an index's prefix search to its default settings.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/INDEX_UID/settings/facet-search'
```
```javascript JS
client.index('INDEX_NAME').resetFacetSearch();
```
```python Python
client.index('books').reset_facet_search_settings()
```
```php PHP
$client->index('INDEX_NAME')->resetFacetSearch();
```
```ruby Ruby
client.index('INDEX_UID').reset_facet_search_setting
```
```go Go
client.Index("books").ResetFacetSearch()
```
```rust Rust
let task: TaskInfo = client
.index(INDEX_UID)
.reset_facet_search()
.await
.unwrap();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "INDEX_UID",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2024-07-19T22:35:33.723983Z"
}
```
Use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Ranking rules
Ranking rules are built-in rules that rank search results according to certain criteria. They are applied in the same order in which they appear in the `rankingRules` array.
[To learn more about ranking rules, refer to our dedicated guide.](/learn/relevancy/relevancy)
### Ranking rules array
| Name | Description |
| :---------------- | :------------------------------------------------------------------------------ |
| **`"words"`** | Sorts results by decreasing number of matched query terms |
| **`"typo"`** | Sorts results by increasing number of typos |
| **`"proximity"`** | Sorts results by increasing distance between matched query terms |
| **`"attribute"`** | Sorts results based on the attribute ranking order |
| **`"sort"`** | Sorts results based on parameters decided at query time |
| **`"exactness"`** | Sorts results based on the similarity of the matched words with the query words |
#### Default order
```json
[
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness"
]
```
### Get ranking rules
Get the ranking rules of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/movies/settings/ranking-rules'
```
```javascript JS
client.index('movies').getRankingRules()
```
```python Python
client.index('movies').get_ranking_rules()
```
```php PHP
$client->index('movies')->getRankingRules();
```
```java Java
client.index("movies").getRankingRulesSettings();
```
```ruby Ruby
client.index('movies').ranking_rules
```
```go Go
client.Index("movies").GetRankingRules()
```
```csharp C#
await client.Index("movies").GetRankingRulesAsync();
```
```rust Rust
let ranking_rules: Vec = client
.index("movies")
.get_ranking_rules()
.await
.unwrap();
```
```swift Swift
client.index("movies").getRankingRules { (result) in
switch result {
case .success(let rankingRules):
print(rankingRules)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').getRankingRules();
```
##### Response: `200 Ok`
```json
[
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"release_date:desc"
]
```
### Update ranking rules
Update the ranking rules of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
[, , …]
```
An array that contains ranking rules in order of importance.
To create a custom ranking rule, give an attribute followed by a colon (`:`) and either `asc` for ascending order or `desc` for descending order.
* To apply an **ascending sort** (results sorted by increasing value): `attribute_name:asc`
* To apply a **descending sort** (results sorted by decreasing value): `attribute_name:desc`
If some documents do not contain the attribute defined in a custom ranking rule, the application of the ranking rule is undefined and the search results might not be sorted as you expected.
Make sure that any attribute used in a custom ranking rule is present in all of your documents. For example, if you set the custom ranking rule `desc(year)`, make sure that all your documents contain the attribute `year`.
[To learn more about ranking rules, refer to our dedicated guide.](/learn/relevancy/ranking_rules)
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/movies/settings/ranking-rules' \
-H 'Content-Type: application/json' \
--data-binary '[
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"release_date:asc",
"rank:desc"
]'
```
```javascript JS
client.index('movies').updateRankingRules([
'words',
'typo',
'proximity',
'attribute',
'sort',
'exactness',
'release_date:asc',
'rank:desc'
])
```
```python Python
client.index('movies').update_ranking_rules([
'words',
'typo',
'proximity',
'attribute',
'sort',
'exactness',
'release_date:asc',
'rank:desc'
])
```
```php PHP
$client->index('movies')->updateRankingRules([
'words',
'typo',
'proximity',
'attribute',
'sort',
'exactness',
'release_date:asc',
'rank:desc'
]);
```
```java Java
Settings settings = new Settings();
settings.setRankingRules(new String[]
{
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"release_date:asc",
"rank:desc"
});
client.index("movies").updateSettings(settings);
```
```ruby Ruby
client.index('movies').update_ranking_rules([
'words',
'typo',
'proximity',
'attribute',
'sort',
'exactness',
'release_date:asc',
'rank:desc'
])
```
```go Go
rankingRules := []string{
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"release_date:asc",
"rank:desc",
}
client.Index("movies").UpdateRankingRules(&rankingRules)
```
```csharp C#
await client.Index("movies").UpdateRankingRulesAsync(new[]
{
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"release_date:asc",
"rank:desc"
});
```
```rust Rust
let ranking_rules = [
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"release_date:asc",
"rank:desc",
];
let task: TaskInfo = client
.index("movies")
.set_ranking_rules(&ranking_rules)
.await
.unwrap();
```
```swift Swift
let rankingRules: [String] = [
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"release_date:asc",
"rank:desc"
]
client.index("movies").updateRankingRules(rankingRules) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').updateRankingRules([
'words',
'typo',
'proximity',
'attribute',
'sort',
'exactness',
'release_date:asc',
'rank:desc',
]);
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset ranking rules
Reset the ranking rules of an index to their [default value](#default-order).
Resetting ranking rules is not the same as removing them. To remove a ranking rule, use the [update ranking rules endpoint](#update-ranking-rules).
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/movies/settings/ranking-rules'
```
```javascript JS
client.index('movies').resetRankingRules()
```
```python Python
client.index('movies').reset_ranking_rules()
```
```php PHP
$client->index('movies')->resetRankingRules();
```
```java Java
client.index("movies").resetRankingRulesSettings();
```
```ruby Ruby
client.index('movies').reset_ranking_rules
```
```go Go
client.Index("movies").ResetRankingRules()
```
```csharp C#
await client.Index("movies").ResetRankingRulesAsync();
```
```rust Rust
let task: TaskInfo = client
.index("movies")
.reset_ranking_rules()
.await
.unwrap();
```
```swift Swift
client.index("movies").resetRankingRules { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').resetRankingRules();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Searchable attributes
The values associated with attributes in the `searchableAttributes` list are searched for matching query words. The order of the list also determines the [attribute ranking order](/learn/relevancy/attribute_ranking_order).
By default, the `searchableAttributes` array is equal to all fields in your dataset. This behavior is represented by the value `["*"]`.
Updating searchable attributes will re-index all documents in the index, which can take some time. We recommend updating your index settings first and then adding documents as this reduces RAM consumption.
[To learn more about searchable attributes, refer to our dedicated guide.](/learn/relevancy/displayed_searchable_attributes#searchable-fields)
### Get searchable attributes
Get the searchable attributes of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/movies/settings/searchable-attributes'
```
```javascript JS
client.index('movies').getSearchableAttributes()
```
```python Python
client.index('movies').get_searchable_attributes()
```
```php PHP
$client->index('movies')->getSearchableAttributes();
```
```java Java
client.index("movies").getSearchableAttributesSettings();
```
```ruby Ruby
client.index('movies').searchable_attributes
```
```go Go
client.Index("movies").GetSearchableAttributes()
```
```csharp C#
await client.Index("movies").GetSearchableAttributesAsync();
```
```rust Rust
let searchable_attributes: Vec = client
.index("movies")
.get_searchable_attributes()
.await
.unwrap();
```
```swift Swift
client.index("movies").getSearchableAttributes { (result) in
switch result {
case .success(let searchableAttributes):
print(searchableAttributes)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').getSearchableAttributes();
```
##### Response: `200 Ok`
```json
[
"title",
"overview",
"genres",
"release_date.year"
]
```
### Update searchable attributes
Update the searchable attributes of an index.
Due to an implementation bug, manually updating `searchableAttributes` will change the displayed order of document fields in the JSON response. This behavior is inconsistent and will be fixed in a future release.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
[, , …]
```
An array of strings. Each string should be an attribute that exists in the selected index. The array should be given in [order of importance](/learn/relevancy/attribute_ranking_order): from the most important attribute to the least important attribute.
If an attribute contains an object, you can use dot notation to set one or more of its keys as a value for this setting: `"searchableAttributes": ["release_date.year"]`.
If the field does not exist, no error will be thrown.
[To learn more about searchable attributes, refer to our dedicated guide.](/learn/relevancy/displayed_searchable_attributes#searchable-fields)
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/movies/settings/searchable-attributes' \
-H 'Content-Type: application/json' \
--data-binary '[
"title",
"overview",
"genres"
]'
```
```javascript JS
client.index('movies').updateSearchableAttributes([
'title',
'overview',
'genres'
])
```
```python Python
client.index('movies').update_searchable_attributes([
'title',
'overview',
'genres'
])
```
```php PHP
$client->index('movies')->updateSearchableAttributes([
'title',
'overview',
'genres'
]);
```
```java Java
client.index("movies").updateSearchableAttributesSettings(new String[]
{
"title",
"overview",
"genres"
});
```
```ruby Ruby
client.index('movies').update_searchable_attributes([
'title',
'overview',
'genres'
])
```
```go Go
searchableAttributes := []string{
"title",
"overview",
"genres",
}
client.Index("movies").UpdateSearchableAttributes(&searchableAttributes)
```
```csharp C#
await client.Index("movies").UpdateSearchableAttributesAsync(new[] {"title", "overview", "genres"});
```
```rust Rust
let searchable_attributes = [
"title",
"overview",
"genres"
];
let task: TaskInfo = client
.index("movies")
.set_searchable_attributes(&searchable_attributes)
.await
.unwrap();
```
```swift Swift
let searchableAttributes: [String] = ["title", "overview", "genres"]
client.index("movies").updateSearchableAttributes(searchableAttributes) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client
.index('movies')
.updateSearchableAttributes(['title', 'overview', 'genres']);
```
In this example, a document with a match in `title` will be more relevant than another document with a match in `overview`.
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset searchable attributes
Reset the searchable attributes of the index to the default value.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/movies/settings/searchable-attributes'
```
```javascript JS
client.index('movies').resetSearchableAttributes()
```
```python Python
client.index('movies').reset_searchable_attributes()
```
```php PHP
$client->index('movies')->resetSearchableAttributes();
```
```java Java
client.index("movies").resetSearchableAttributesSettings();
```
```ruby Ruby
client.index('movies').reset_searchable_attributes
```
```go Go
client.Index("movies").ResetSearchableAttributes()
```
```csharp C#
await client.Index("movies").ResetSearchableAttributesAsync();
```
```rust Rust
let task: TaskInfo = client
.index("movies")
.reset_searchable_attributes()
.await
.unwrap();
```
```swift Swift
client.index("movies").resetSearchableAttributes { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').resetSearchableAttributes();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Search cutoff
Configure the maximum duration of a search query. Meilisearch will interrupt any search taking longer than the cutoff value.
By default, Meilisearch interrupts searches after 1500 milliseconds.
### Get search cutoff
Get an index's search cutoff value.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/movies/settings/search-cutoff-ms'
```
```javascript JS
client.index('movies').getSearchCutoffMs()
```
```python Python
client.index('movies').get_search_cutoff_ms()
```
```php PHP
$client->index('movies')->getSearchCutoffMs();
```
```java Java
client.index("movies").getSearchCutoffMsSettings();
```
```ruby Ruby
client.index('movies').search_cutoff_ms
```
```go Go
client.Index("movies").GetSearchCutoffMs()
```
```csharp C#
var searchCutoff = await client.Index("movies").GetSearchCutoffMsAsync();
```
```rust Rust
let search_cutoff_ms: String = client
.index("movies")
.get_search_cutoff_ms()
.await
.unwrap();
```
```swift Swift
let precisionValue = try await self.client.index("books").getSearchCutoffMs()
```
##### Response: `200 Ok`
```json
null
```
### Update search cutoff
Update an index's search cutoff value.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
150
```
A single integer indicating the cutoff value in milliseconds.
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/movies/settings/search-cutoff-ms' \
-H 'Content-Type: application/json' \
--data-binary '150'
```
```javascript JS
client.index('movies').updateSearchCutoffMs(150)
```
```python Python
client.index('movies').update_search_cutoff_ms(150)
```
```php PHP
$client->index('movies')->updateSearchCutoffMs(150);
```
```java Java
client.index("movies").updateSearchCutoffMsSettings(150);
```
```ruby Ruby
client.index('movies').update_search_cutoff_ms(150)
```
```go Go
client.Index("movies").UpdateSearchCutoffMs(150)
```
```csharp C#
await client.Index("movies").UpdateSearchCutoffMsAsync(150);
```
```rust Rust
let task: TaskInfo = client
.index("movies")
.set_search_cutoff_ms(Some(150))
.await
.unwrap();
```
```swift Swift
let task = try await self.client.index("books").updateSearchCutoffMs(150)
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2023-03-21T06:33:41.000000Z"
}
```
Use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset search cutoff
Reset an index's search cutoff value to its default value, `null`. This translates to a cutoff of 1500ms.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/movies/settings/search-cutoff-ms'
```
```javascript JS
client.index('movies').resetSearchCutoffMs()
```
```python Python
client.index('movies').reset_search_cutoff_ms()
```
```php PHP
$client->index('movies')->resetSearchCutoffMs();
```
```java Java
client.index("movies").resetSearchCutoffMsSettings();
```
```ruby Ruby
client.index('movies').reset_search_cutoff_ms
```
```go Go
client.Index("books").ResetSearchCutoffMs()
```
```csharp C#
await client.Index("movies").ResetSearchCutoffMsAsync();
```
```rust Rust
let task: TaskInfo = client
.index("movies")
.reset_search_cutoff_ms()
.await
.unwrap();
```
```swift Swift
let task = try await self.client.index("books").resetSearchCutoffMs()
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2023-03-21T07:05:16.000000Z"
}
```
## Separator tokens
Configure strings as custom separator tokens indicating where a word ends and begins.
Tokens in the `separatorTokens` list are added on top of [Meilisearch's default list of separators](/learn/engine/datatypes#string). To remove separators from the default list, use [the `nonSeparatorTokens` setting](#non-separator-tokens).
### Get separator tokens
Get an index's list of custom separator tokens.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/articles/settings/separator-tokens'
```
```javascript JS
client.index('books').getSeparatorTokens()
```
```python Python
client.index('articles').get_separator_tokens()
```
```php PHP
$client->index('articles')->getSeparatorTokens();
```
```java Java
client.index("articles").getSeparatorTokensSettings();
```
```ruby Ruby
client.index('articles').separator_tokens
```
```go Go
client.Index("articles").GetSeparatorTokens()
```
```csharp C#
await client.Index("movies").GetSeparatorTokensAsync();
```
```rust Rust
let task: TaskInfo = client
.index('articles')
.get_separator_tokens()
.await
.unwrap();
```
```swift Swift
client.index("books").getSeparatorTokens { result in
// handle result
}
```
```dart Dart
await client.index('articles').getSeparatorTokens();
```
##### Response: `200 Ok`
```json
[]
```
### Update separator tokens
Update an index's list of custom separator tokens.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
["|", "…"]
```
An array of strings, with each string indicating a word separator.
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/articles/settings/separator-tokens' \
-H 'Content-Type: application/json' \
--data-binary '["|", "…"]'
```
```javascript JS
client.index('books').updateSeparatorTokens(['|', '…'])
```
```python Python
client.index('articles').update_separator_tokens(["|", "…"])
```
```php PHP
$client->index('articles')->updateSeparatorTokens(['|', '…']);
```
```java Java
String[] newSeparatorTokens = { "|", "…" };
client.index("articles").updateSeparatorTokensSettings(newSeparatorTokens);
```
```ruby Ruby
client.index('articles').update_separator_tokens(['|', '…'])
```
```go Go
client.Index("articles").UpdateSeparatorTokens([]string{
"|",
"…",
})
```
```csharp C#
await client.Index("movies").UpdateSeparatorTokensAsync(new[] { "|", "…" });
```
```rust Rust
let task: TaskInfo = client
.index('articles')
.set_separator_tokens(&vec!['|'.to_string(), '…'.to_string()])
.await
.unwrap();
```
```swift Swift
client.index("books").updateSeparatorTokens(["|", "…"]) { result in
// handle result
}
```
```dart Dart
await client.index('articles').updateSeparatorTokens(["|", "…"]);
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
Use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset separator tokens
Reset an index's list of custom separator tokens to its default value, `[]`.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/articles/settings/separator-tokens'
```
```javascript JS
client.index('books').resetSeparatorTokens()
```
```python Python
client.index('articles').reset_separator_tokens()
```
```php PHP
$client->index('articles')->resetSeparatorTokens();
```
```java Java
client.index("articles").resetSeparatorTokensSettings();
```
```ruby Ruby
client.index('articles').reset_separator_tokens
```
```go Go
client.Index("articles").ResetSeparatorTokens()
```
```csharp C#
await client.Index("movies").ResetSeparatorTokensAsync();
```
```rust Rust
let task: TaskInfo = client
.index('articles')
.reset_separator_tokens()
.await
.unwrap();
```
```swift Swift
client.index("books").resetSeparatorTokens { result in
// handle result
}
```
```dart Dart
await client.index('articles').resetSeparatorTokens();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
Use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Non-separator tokens
Remove tokens from Meilisearch's default [list of word separators](/learn/engine/datatypes#string).
### Get non-separator tokens
Get an index's list of non-separator tokens.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/articles/settings/non-separator-tokens'
```
```javascript JS
client.index('books').getNonSeparatorTokens()
```
```python Python
client.index('articles').get_non_separator_tokens()
```
```php PHP
$client->index('articles')->getNonSeparatorTokens();
```
```java Java
client.index("articles").getNonSeparatorTokensSettings();
```
```ruby Ruby
client.index('articles').non_separator_tokens
```
```go Go
client.Index("articles").GetNonSeparatorTokens()
```
```csharp C#
await client.Index("movies").GetNonSeparatorTokensAsync();
```
```rust Rust
let task: TaskInfo = client
.index('articles')
.get_non_separator_tokens()
.await
.unwrap();
```
```swift Swift
client.index("books").getNonSeparatorTokens { result in
// handle result
}
```
```dart Dart
await client.index('articles').getNonSeparatorTokens();
```
##### Response: `200 Ok`
```json
[]
```
### Update non-separator tokens
Update an index's list of non-separator tokens.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
["@", "#"]
```
An array of strings, with each string indicating a token present in [list of word separators](/learn/engine/datatypes#string).
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/articles/settings/non-separator-tokens' \
-H 'Content-Type: application/json' \
--data-binary '["@", "#"]'
```
```javascript JS
client.index('books').updateNonSeparatorTokens(['@', '#'])
```
```python Python
client.index('articles').update_non_separator_tokens(["@", "#"])
```
```php PHP
$client->index('articles')->updateNonSeparatorTokens(['@', '#']);
```
```java Java
String[] newSeparatorTokens = { "@", "#" };
client.index("articles").updateNonSeparatorTokensSettings(newSeparatorTokens);
```
```ruby Ruby
client.index('articles').update_non_separator_tokens(['@', '#'])
```
```go Go
client.Index("articles").UpdateNonSeparatorTokens([]string{
"@",
"#",
})
```
```csharp C#
await client.Index("movies").UpdateNonSeparatorTokensAsync(new[] { "@", "#" });
```
```rust Rust
let task: TaskInfo = client
.index('articles')
.set_non_separator_tokens(&vec!['@'.to_string(), '#'.to_string()])
.await
.unwrap();
```
```swift Swift
client.index("books").updateNonSeparatorTokens(["@", "#"]) { result in
// handle result
}
```
```dart Dart
await client.index('articles').updateNonSeparatorTokens(["@", "#"]);
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
Use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset non-separator tokens
Reset an index's list of non-separator tokens to its default value, `[]`.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/articles/settings/separator-tokens'
```
```javascript JS
client.index('books').resetSeparatorTokens()
```
```python Python
client.index('articles').reset_separator_tokens()
```
```php PHP
$client->index('articles')->resetSeparatorTokens();
```
```java Java
client.index("articles").resetSeparatorTokensSettings();
```
```ruby Ruby
client.index('articles').reset_separator_tokens
```
```go Go
client.Index("articles").ResetSeparatorTokens()
```
```csharp C#
await client.Index("movies").ResetSeparatorTokensAsync();
```
```rust Rust
let task: TaskInfo = client
.index('articles')
.reset_separator_tokens()
.await
.unwrap();
```
```swift Swift
client.index("books").resetSeparatorTokens { result in
// handle result
}
```
```dart Dart
await client.index('articles').resetSeparatorTokens();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
Use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Sortable attributes
Attributes that can be used when sorting search results using the [`sort` search parameter](/reference/api/search#sort).
Updating sortable attributes will re-index all documents in the index, which can take some time. We recommend updating your index settings first and then adding documents as this reduces RAM consumption.
[To learn more about sortable attributes, refer to our dedicated guide.](/learn/filtering_and_sorting/sort_search_results)
### Get sortable attributes
Get the sortable attributes of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/books/settings/sortable-attributes'
```
```javascript JS
client.index('books').getSortableAttributes()
```
```python Python
client.index('books').get_sortable_attributes()
```
```php PHP
$client->index('books')->getSortableAttributes();
```
```java Java
client.index("books").getSortableAttributesSettings();
```
```ruby Ruby
client.index('books').sortable_attributes
```
```go Go
client.Index("books").GetSortableAttributes()
```
```csharp C#
await client.Index("books").GetSortableAttributesAsync();
```
```rust Rust
let sortable_attributes: Vec = client
.index("books")
.get_sortable_attributes()
.await
.unwrap();
```
```swift Swift
client.index("books").getSortableAttributes { (result: Result, Swift.Error>) in
switch result {
case .success(let attributes):
print(attributes)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('books').getSortableAttributes();
```
##### Response: `200 Ok`
```json
[
"price",
"author.surname"
]
```
### Update sortable attributes
Update an index's sortable attributes list.
[You can read more about sorting at query time on our dedicated guide.](/learn/filtering_and_sorting/sort_search_results)
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
[, , …]
```
An array of strings. Each string should be an attribute that exists in the selected index.
If an attribute contains an object, you can use dot notation to set one or more of its keys as a value for this setting: `"sortableAttributes": ["author.surname"]`.
If the field does not exist, no error will be thrown.
[To learn more about sortable attributes, refer to our dedicated guide.](/learn/filtering_and_sorting/sort_search_results)
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/books/settings/sortable-attributes' \
-H 'Content-Type: application/json' \
--data-binary '[
"price",
"author"
]'
```
```javascript JS
client.index('books')
.updateSortableAttributes([
'price',
'author'
])
```
```python Python
client.index('books').update_sortable_attributes([
'price',
'author'
])
```
```php PHP
$client->index('books')->updateSortableAttributes([
'price',
'author'
]);
```
```java Java
client.index("books").updateSortableAttributesSettings(new String[] {"price", "author"});
```
```ruby Ruby
client.index('books').update_sortable_attributes([
'price',
'author'
])
```
```go Go
sortableAttributes := []string{
"price",
"author",
}
client.Index("books").UpdateSortableAttributes(&sortableAttributes)
```
```csharp C#
await client.Index("books").UpdateSortableAttributesAsync(new [] { "price", "author" });
```
```rust Rust
let sortable_attributes = [
"price",
"author"
];
let task: TaskInfo = client
.index("books")
.set_sortable_attributes(&sortable_attributes)
.await
.unwrap();
```
```swift Swift
client.index("books").updateSortableAttributes(["price", "author"]) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('books').updateSortableAttributes(['price', 'author']);
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset sortable attributes
Reset an index's sortable attributes list back to its default value.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/books/settings/sortable-attributes'
```
```javascript JS
client.index('books').resetSortableAttributes()
```
```python Python
client.index('books').reset_sortable_attributes()
```
```php PHP
$client->index('books')->resetSortableAttributes();
```
```java Java
client.index("books").resetSortableAttributesSettings();
```
```ruby Ruby
client.index('books').reset_sortable_attributes
```
```go Go
client.Index("books").ResetSortableAttributes()
```
```csharp C#
await client.Index("books").ResetSortableAttributesAsync();
```
```rust Rust
let task: TaskInfo = client
.index("books")
.reset_sortable_attributes()
.await
.unwrap();
```
```swift Swift
client.index("books").resetSortableAttributes() { (result) in
switch result {
case .success(let attributes):
print(attributes)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('books').resetSortableAttributes();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Stop words
Words added to the `stopWords` list are ignored in future search queries.
Updating stop words will re-index all documents in the index, which can take some time. We recommend updating your index settings first and then adding documents as this reduces RAM consumption.
Stop words are strongly related to the language used in your dataset. For example, most datasets containing English documents will have countless occurrences of `the` and `of`. Italian datasets, instead, will benefit from ignoring words like `a`, `la`, or `il`.
[This open-source project on GitHub](https://github.com/stopwords-iso/stopwords-iso) offers lists of possible stop words in different languages. Note that, depending on your dataset and use case, you will need to tweak these lists for optimal results.
### Get stop words
Get the stop words list of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/movies/settings/stop-words'
```
```javascript JS
client.index('movies').getStopWords()
```
```python Python
client.index('movies').get_stop_words()
```
```php PHP
$client->index('movies')->getStopWords();
```
```java Java
client.index("movies").getStopWordsSettings();
```
```ruby Ruby
client.index('movies').stop_words
```
```go Go
client.Index("movies").GetStopWords()
```
```csharp C#
await client.Index("movies").GetStopWordsAsync();
```
```rust Rust
let stop_words: Vec = client
.index("movies")
.get_stop_words()
.await
.unwrap();
```
```swift Swift
client.index("movies").getStopWords { (result) in
switch result {
case .success(let stopWords):
print(stopWords)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').getStopWords();
```
##### Response: `200 Ok`
```json
[
"of",
"the",
"to"
]
```
### Update stop words
Update the list of stop words of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
[, , …]
```
An array of strings. Each string should be a single word.
If a list of stop words already exists, it will be overwritten (*replaced*).
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/movies/settings/stop-words' \
-H 'Content-Type: application/json' \
--data-binary '[
"the",
"of",
"to"
]'
```
```javascript JS
client.index('movies').updateStopWords(['of', 'the', 'to'])
```
```python Python
client.index('movies').update_stop_words(['of', 'the', 'to'])
```
```php PHP
$client->index('movies')->updateStopWords(['the', 'of', 'to']);
```
```java Java
client.index("movies").updateStopWordsSettings(new String[] {"of", "the", "to"});
```
```ruby Ruby
client.index('movies').update_stop_words(['of', 'the', 'to'])
```
```go Go
stopWords := []string{"of", "the", "to"}
client.Index("movies").UpdateStopWords(&stopWords)
```
```csharp C#
await client.Index("movies").UpdateStopWordsAsync(new[] {"of", "the", "to"});
```
```rust Rust
let stop_words = ["of", "the", "to"];
let task: TaskInfo = client
.index("movies")
.set_stop_words(&stop_words)
.await
.unwrap();
```
```swift Swift
let stopWords: [String] = ["of", "the", "to"]
client.index("movies").updateStopWords(stopWords) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').updateStopWords(['of', 'the', 'to']);
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset stop words
Reset the list of stop words of an index to its default value.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/movies/settings/stop-words'
```
```javascript JS
client.index('movies').resetStopWords()
```
```python Python
client.index('movies').reset_stop_words()
```
```php PHP
$client->index('movies')->resetStopWords();
```
```java Java
client.index("movies").resetStopWordsSettings();
```
```ruby Ruby
client.index('movies').reset_stop_words
```
```go Go
client.Index("movies").ResetStopWords()
```
```csharp C#
await client.Index("movies").ResetStopWordsAsync();
```
```rust Rust
let task: TaskInfo = client
.index("movies")
.reset_stop_words()
.await
.unwrap();
```
```swift Swift
client.index("movies").resetStopWords { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').resetStopWords();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Synonyms
The `synonyms` object contains words and their respective synonyms. A synonym in Meilisearch is considered equal to its associated word for the purposes of calculating search results.
[To learn more about synonyms, refer to our dedicated guide.](/learn/relevancy/synonyms)
### Get synonyms
Get the list of synonyms of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/movies/settings/synonyms'
```
```javascript JS
client.index('movies').getSynonyms()
```
```python Python
client.index('movies').get_synonyms()
```
```php PHP
$client->index('movies')->getSynonyms();
```
```java Java
client.index("movies").getSynonymsSettings();
```
```ruby Ruby
client.index('movies').synonyms
```
```go Go
client.Index("movies").GetSynonyms()
```
```csharp C#
await client.Index("movies").GetSynonymsAsync();
```
```rust Rust
let synonyms: HashMap> = client
.index("movies")
.get_synonyms()
.await
.unwrap();
```
```swift Swift
client.index("movies").getSynonyms { (result) in
switch result {
case .success(let synonyms):
print(synonyms)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').getSynonyms();
```
##### Response: `200 OK`
```json
{
"wolverine": [
"xmen",
"logan"
],
"logan": [
"wolverine",
"xmen"
],
"wow": [
"world of warcraft"
]
}
```
### Update synonyms
Update the list of synonyms of an index. Synonyms are [normalized](/learn/relevancy/synonyms#normalization).
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
{
: [, , …],
…
}
```
An object that contains all synonyms and their associated words. Add the associated words in an array to set a synonym for a word.
[To learn more about synonyms, refer to our dedicated guide.](/learn/relevancy/synonyms)
#### Example
```bash cURL
curl \
-X PUT 'MEILISEARCH_URL/indexes/movies/settings/synonyms' \
-H 'Content-Type: application/json' \
--data-binary '{
"wolverine": [
"xmen",
"logan"
],
"logan": [
"wolverine",
"xmen"
],
"wow": ["world of warcraft"]
}'
```
```javascript JS
client.index('movies').updateSynonyms({
wolverine: ['xmen', 'logan'],
logan: ['wolverine', 'xmen'],
wow: ['world of warcraft']
})
```
```python Python
client.index('movies').update_synonyms({
'wolverine': ['xmen', 'logan'],
'logan': ['wolverine', 'xmen'],
'wow': ['world of warcraft']
})
```
```php PHP
$client->index('movies')->updateSynonyms([
'wolverine' => ['xmen', 'logan'],
'logan' => ['wolverine', 'xmen'],
'wow' => ['world of warcraft']
]);
```
```java Java
HashMap synonyms = new HashMap();
synonyms.put("wolverine", new String[] {"xmen", "logan"});
synonyms.put("logan", new String[] {"wolverine"});
client.index("movies").updateSynonymsSettings(synonyms);
```
```ruby Ruby
client.index('movies').update_synonyms({
wolverine: ['xmen', 'logan'],
logan: ['wolverine', 'xmen'],
wow: ['world of warcraft']
})
```
```go Go
synonyms := map[string][]string{
"wolverine": []string{"xmen", "logan"},
"logan": []string{"wolverine", "xmen"},
"wow": []string{"world of warcraft"},
}
client.Index("movies").UpdateSynonyms(&synonyms)
```
```csharp C#
var synonyms = new Dictionary>
{
{ "wolverine", new string[] { "xmen", "logan" } },
{ "logan", new string[] { "wolverine", "xmen" } },
{ "wow", new string[] { "world of warcraft" } }
};
await client.Index("movies").UpdateSynonymsAsync(synonyms);
```
```rust Rust
let mut synonyms = std::collections::HashMap::new();
synonyms.insert(String::from("wolverine"), vec![String::from("xmen"), String::from("logan")]);
synonyms.insert(String::from("logan"), vec![String::from("xmen"), String::from("wolverine")]);
synonyms.insert(String::from("wow"), vec![String::from("world of warcraft")]);
let task: TaskInfo = client
.index("movies")
.set_synonyms(&synonyms)
.await
.unwrap();
```
```swift Swift
let synonyms: [String: [String]] = [
"wolverine": ["xmen", "logan"],
"logan": ["wolverine", "xmen"],
"wow": ["world of warcraft"]
]
client.index("movies").updateSynonyms(synonyms) { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').updateSynonyms({
'wolverine': ['xmen', 'logan'],
'logan': ['wolverine', 'xmen'],
'wow': ['world of warcraft'],
});
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset synonyms
Reset the list of synonyms of an index to its default value.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/movies/settings/synonyms'
```
```javascript JS
client.index('movies').resetSynonyms()
```
```python Python
client.index('movies').reset_synonyms()
```
```php PHP
$client->index('movies')->resetSynonyms();
```
```java Java
client.index("movies").resetSynonymsSettings();
```
```ruby Ruby
client.index('movies').reset_synonyms
```
```go Go
client.Index("movies").ResetSynonyms()
```
```csharp C#
await client.Index("movies").ResetSynonymsAsync();
```
```rust Rust
let task: TaskInfo = client
.index("movies")
.reset_synonyms()
.await
.unwrap();
```
```swift Swift
client.index("movies").resetSynonyms { (result) in
switch result {
case .success(let task):
print(task)
case .failure(let error):
print(error)
}
}
```
```dart Dart
await client.index('movies').resetSynonyms();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "movies",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2021-08-11T09:25:53.000000Z"
}
```
You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Typo tolerance
Typo tolerance helps users find relevant results even when their search queries contain spelling mistakes or typos. This setting allows you to configure the minimum word size for typos and disable typo tolerance for specific words or attributes.
[To learn more about typo tolerance, refer to our dedicated guide.](/learn/relevancy/typo_tolerance_settings)
### Typo tolerance object
| Name | Type | Default Value | Description |
| :--------------------------------- | :--------------- | :------------ | :------------------------------------------------------------------------------- |
| **`enabled`** | Boolean | `true` | Whether typo tolerance is enabled or not |
| **`minWordSizeForTypos.oneTypo`** | Integer | `5` | The minimum word size for accepting 1 typo; must be between `0` and `twoTypos` |
| **`minWordSizeForTypos.twoTypos`** | Integer | `9` | The minimum word size for accepting 2 typos; must be between `oneTypo` and `255` |
| **`disableOnWords`** | Array of strings | Empty | An array of words for which the typo tolerance feature is disabled |
| **`disableOnAttributes`** | Array of strings | Empty | An array of attributes for which the typo tolerance feature is disabled |
| **`disableOnNumbers`** | Boolean | `false` | Whether typo tolerance for numbers is disabled or enabled |
### Get typo tolerance settings
Get the typo tolerance settings of an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/books/settings/typo-tolerance'
```
```javascript JS
client.index('books').getTypoTolerance()
```
```python Python
client.index('books').get_typo_tolerance()
```
```php PHP
$client->index('books')->getTypoTolerance();
```
```java Java
client.index("books").getTypoToleranceSettings();
```
```ruby Ruby
index('books').typo_tolerance
```
```go Go
client.Index("books").GetTypoTolerance()
```
```csharp C#
await client.Index("books").GetTypoToleranceAsync();
```
```rust Rust
let typo_tolerance: TypoToleranceSettings = client
.index("books")
.get_typo_tolerance()
.await
.unwrap();
```
```dart Dart
await client.index('books').getTypoTolerance();
```
##### Response: `200 OK`
```json
{
"enabled": true,
"minWordSizeForTypos": {
"oneTypo": 5,
"twoTypos": 9
},
"disableOnWords": [],
"disableOnAttributes": []
}
```
### Update typo tolerance settings
Partially update the typo tolerance settings for an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```
{
"enabled": ,
"minWordSizeForTypos": {
"oneTypo": ,
"twoTypos":
},
"disableOnWords": [, , …],
"disableOnAttributes": [, , …]
"disableOnNumbers": ,
}
```
| Name | Type | Default Value | Description |
| :--------------------------------- | :--------------- | :------------ | :------------------------------------------------------------------------------- |
| **`enabled`** | Boolean | `true` | Whether typo tolerance is enabled or not |
| **`minWordSizeForTypos.oneTypo`** | Integer | `5` | The minimum word size for accepting 1 typo; must be between `0` and `twoTypos` |
| **`minWordSizeForTypos.twoTypos`** | Integer | `9` | The minimum word size for accepting 2 typos; must be between `oneTypo` and `255` |
| **`disableOnWords`** | Array of strings | Empty | An array of words for which the typo tolerance feature is disabled |
| **`disableOnAttributes`** | Array of strings | Empty | An array of attributes for which the typo tolerance feature is disabled |
| **`disableOnNumbers`** | Boolean | `false` | Whether typo tolerance for numbers is disabled or enabled |
#### Example
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/indexes/books/settings/typo-tolerance' \
-H 'Content-Type: application/json' \
--data-binary '{
"minWordSizeForTypos": {
"oneTypo": 4,
"twoTypos": 10
},
"disableOnAttributes": ["title"]
}'
```
```javascript JS
client.index('books').updateTypoTolerance({
minWordSizeForTypos: {
oneTypo: 4,
twoTypos: 10
},
disableOnAttributes: [
'title'
]
})
```
```python Python
client.index('books').update_typo_tolerance({
'minWordSizeForTypos': {
'oneTypo': 4,
'twoTypos': 10
},
'disableOnAttributes': [
'title'
]
})
```
```php PHP
$client->index('books')->updateTypoTolerance([
'minWordSizeForTypos' => [
'oneTypo' => 4,
'twoTypos' => 10
],
'disableOnAttributes' => [
'title'
]
]);
```
```java Java
TypoTolerance typoTolerance = new TypoTolerance();
HashMap minWordSizeTypos =
new HashMap() {
{
put("oneTypo", 4);
put("twoTypos", 10);
}
};
typoTolerance.setMinWordSizeForTypos(minWordSizeTypos);
typoTolerance.setDisableOnAttributes(new String[] {"title"});
client.index("books").updateTypoToleranceSettings(typoTolerance);
```
```ruby Ruby
index('books').update_typo_tolerance({
min_word_size_for_typos: {
one_typo: 4,
two_typos: 10
},
disable_on_attributes: ['title']
})
```
```go Go
client.Index("books").UpdateTypoTolerance(&meilisearch.TypoTolerance{
MinWordSizeForTypos: meilisearch.MinWordSizeForTypos{
OneTypo: 4,
TwoTypos: 10,
},
DisableOnAttributes: []string{"title"},
})
```
```csharp C#
var typoTolerance = new TypoTolerance {
DisableOnAttributes = new string[] { "title" },
MinWordSizeTypos = new TypoTolerance.TypoSize {
OneTypo = 4,
TwoTypos = 10
}
};
await client.Index("books").UpdateTypoToleranceAsync(typoTolerance);
```
```rust Rust
let typo_tolerance = TypoToleranceSettings {
enabled: Some(false),
disable_on_attributes: None,
disable_on_words: None,
min_word_size_for_typos: None,
};
let task: TaskInfo = client
.index("books")
.set_typo_tolerance(&typo_tolerance)
.await
.unwrap();
```
```dart Dart
final toUpdate = TypoTolerance(
minWordSizeForTypos: MinWordSizeForTypos(
oneTypo: 4,
twoTypos: 10,
),
disableOnAttributes: ['title'],
);
await client.index('books').updateTypoTolerance(toUpdate);
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "books",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2022-04-14T20:56:44.991039Z"
}
```
You can use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset typo tolerance settings
Reset an index's typo tolerance settings to their [default value](#typo-tolerance-object).
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/books/settings/typo-tolerance'
```
```javascript JS
client.index('books').resetTypoTolerance()
```
```python Python
client.index('books').reset_typo_tolerance()
```
```php PHP
$client->index('books')->resetTypoTolerance();
```
```java Java
client.index("books").resetTypoToleranceSettings();
```
```ruby Ruby
index('books').reset_typo_tolerance
```
```go Go
client.Index("books").ResetTypoTolerance()
```
```csharp C#
await client.Index("books").ResetTypoToleranceAsync();
```
```rust Rust
let task: TaskInfo = client
.index("books")
.reset_typo_tolerance()
.await
.unwrap();
```
```dart Dart
await client.index('books').resetTypoTolerance();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "books",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2022-04-14T20:53:32.863107Z"
}
```
You can use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Embedders
Embedders translate documents and queries into vector embeddings. You must configure at least one embedder to use AI-powered search.
### Embedders object
The embedders object may contain up to 256 embedder objects. Each embedder object must be assigned a unique name:
```json
{
"default": {
"source": "huggingFace",
"model": "BAAI/bge-base-en-v1.5",
"documentTemplate": "A movie titled '{{doc.title}}' whose description starts with {{doc.overview|truncatewords: 20}}"
},
"openai": {
"source": "openAi",
"apiKey": "OPENAI_API_KEY",
"model": "text-embedding-3-small",
"documentTemplate": "A movie titled {{doc.title}} whose description starts with {{doc.overview|truncatewords: 20}}",
}
}
```
These embedder objects may contain the following fields:
| Name | Type | Default Value | Description |
| ------------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`source`** | String | Empty | The third-party tool that will generate embeddings from documents. Must be `openAi`, `huggingFace`, `ollama`, `rest`, or `userProvided` |
| **`url`** | String | `http://localhost:11434/api/embeddings` | The URL Meilisearch contacts when querying the embedder |
| **`apiKey`** | String | Empty | Authentication token Meilisearch should send with each request to the embedder. If not present, Meilisearch will attempt to read it from environment variables |
| **`model`** | String | Empty | The model your embedder uses when generating vectors |
| **`documentTemplate`** | String | `{% for field in fields %} {% if field.is_searchable and not field.value == nil %}{{ field.name }}: {{ field.value }} {% endif %} {% endfor %}` | Template defining the data Meilisearch sends to the embedder |
| **`documentTemplateMaxBytes`** | Integer | `400` | Maximum allowed size of rendered document template |
| **`dimensions`** | Integer | Empty | Number of dimensions in the chosen model. If not supplied, Meilisearch tries to infer this value |
| **`revision`** | String | Empty | Model revision hash |
| **`distribution`** | Object | Empty | Describes the natural distribution of search results. Must contain two fields, `mean` and `sigma`, each containing a numeric value between `0` and `1` |
| **`request`** | Object | Empty | A JSON value representing the request Meilisearch makes to the remote embedder |
| **`response`** | Object | Empty | A JSON value representing the response Meilisearch expects from the remote embedder |
| **`binaryQuantized`** | Boolean | Empty | Once set to `true`, irreversibly converts all vector dimensions to 1-bit values |
| **`indexingEmbedder`** | Object | Empty | Configures embedder to vectorize documents during indexing |
| **`searchEmbedder`** | Object | Empty | Configures embedder to vectorize search queries |
| **`pooling`** | String | `"useModel"` | Pooling method for Hugging Face embedders |
| **`indexingFragments`** | Object | Empty | Configures multimodal embedding generation at indexing time |
| **`searchFragments`** | Object | Empty | Configures data handling during multimodal search |
### Get embedder settings
Get the embedders configured for an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X GET 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/embedders'
```
```ruby Ruby
client.index('INDEX_NAME').embedders
```
```rust Rust
let embedders = index.get_embedders().await.unwrap();
```
##### Response: `200 OK`
```json
{
"default": {
"source": "openAi",
"apiKey": "OPENAI_API_KEY",
"model": "text-embedding-3-small",
"documentTemplate": "A movie titled {{doc.title}} whose description starts with {{doc.overview|truncatewords: 20}}",
"dimensions": 1536
}
}
```
### Update embedder settings
Partially update the embedder settings for an index. When this setting is updated Meilisearch may reindex all documents and regenerate their embeddings.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```json
{
"default": {
"source": ,
"url": ,
"apiKey": ,
"model": ,
"documentTemplate": ,
"documentTemplateMaxBytes": ,
"dimensions": ,
"revision": ,
"distribution": {
"mean": ,
"sigma":
},
"request": { … },
"response": { … },
"headers": { … },
"binaryQuantized": ,
"pooling": ,
"indexingEmbedder": { … },
"searchEmbedder": { … }
}
}
```
Set an embedder to `null` to remove it from the embedders list.
##### `source`
Use `source` to configure an embedder's source. The source corresponds to a service that generates embeddings from your documents.
Meilisearch supports the following sources:
* `openAi`
* `huggingFace`
* `ollama`
* `rest`
* `userProvided`
* `composite`
`rest` is a generic source compatible with any embeddings provider offering a REST API.
Use `userProvided` when you want to generate embeddings manually. In this case, you must include vector data in your documents' `_vectors` field. You must also generate vectors for search queries.
This field is mandatory.
###### Composite embedders
Choose `composite` to use one embedder during indexing time, and another embedder at search time. Must be used together with [`indexingEmbedder` and `searchEmbedder`](#indexingembedder-and-searchembedder).
This is an experimental feature. Use the experimental features endpoint to activate it:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{
"compositeEmbedders": true
}'
```
##### `url`
Meilisearch queries `url` to generate vector embeddings for queries and documents. `url` must point to a REST-compatible embedder. You may also use `url` to work with proxies, such as when targeting `openAi` from behind a proxy.
This field is mandatory when using `rest` embedders.
This field is optional when using `ollama` and `openAi` embedders. `ollama` URLs must end with either `/api/embed` or `/api/embeddings`.
This field is incompatible with `huggingFace` and `userProvided` embedders.
##### `apiKey`
Authentication token Meilisearch should send with each request to the embedder.
This field is mandatory if using a protected `rest` embedder.
This field is optional for `openAI` and `ollama` embedders. If you don't specify `apiKey` when using `openAI`, Meilisearch attempts to read it from the `OPENAI_API_KEY` environment variable.
This field is incompatible with `huggingFace` and `userProvided` embedders.
##### `model`
The model your embedder uses when generating vectors. These are the officially supported models Meilisearch supports:
* `openAi`: `text-embedding-3-small`, `text-embedding-3-large`, `openai-text-embedding-ada-002`
* `huggingFace`: `BAAI/bge-base-en-v1.5`
Other models, such as [HuggingFace's BERT models](https://huggingface.co/models?other=bert) or those provided by Ollama and REST embedders may also be compatible with Meilisearch.
This field is mandatory for `Ollama` embedders.
This field is optional for `openAi` and `huggingFace`. By default, Meilisearch uses `text-embedding-3-small` and `BAAI/bge-base-en-v1.5` respectively.
This field is incompatible with `rest` and `userProvided` embedders.
##### `documentTemplate`
`documentTemplate` is a string containing a [Liquid template](https://shopify.github.io/liquid/basics/introduction). Meillisearch interpolates the template for each document and sends the resulting text to the embedder. The embedder then generates document vectors based on this text.
You may use the following context values:
* `{{doc.FIELD}}`: `doc` stands for the document itself. `FIELD` must correspond to an attribute present on all documents value will be replaced by the value of that field in the input document
* `{{fields}}`: a list of all the `field`s appearing in any document in the index. Each `field` object in this list has the following properties:
* `name`: the field's attribute
* `value`: the field's value
* `is_searchable`: whether the field is present in the searchable attributes list
If a `field` does not exist in a document, its `value` is `nil`.
For best results, build short templates that only contain highly relevant data. If working with a long field, consider [truncating it](https://shopify.github.io/liquid/filters/truncatewords/). If you do not manually set it, `documentTemplate` will include all searchable and non-null document fields. This may lead to suboptimal performance and relevancy.
This field is incompatible with `userProvided` embedders.
This field is optional but strongly encouraged for all other embedders.
##### `documentTemplateMaxBytes`
The maximum size of a rendered document template. Longer texts are truncated to fit the configured limit.
`documentTemplateMaxBytes` must be an integer. It defaults to `400`.
This field is incompatible with `userProvided` embedders.
This field is optional for all other embedders.
##### `dimensions`
Number of dimensions in the chosen model. If not supplied, Meilisearch tries to infer this value.
In most cases, `dimensions` should be the exact same value of your chosen model. Setting `dimensions` to a value lower than the model may lead to performance improvements and is only supported in the following OpenAI models:
* `openAi`: `text-embedding-3-small`, `text-embedding-3-large`
This field is mandatory for `userProvided` embedders.
This field is optional for `openAi`, `huggingFace`, `ollama`, and `rest` embedders.
##### `revision`
Use this field to use a specific revision of a model.
This field is optional for the `huggingFace` embedder.
This field is incompatible with all other embedders.
##### `request`
`request` must be a JSON object with the same structure and data of the request you must send to your `rest` embedder.
The field containing the input text Meilisearch should send to the embedder must be replaced with `"{{text}}"`:
```json
{
"source": "rest",
"request": {
"prompt": "{{text}}"
…
},
…
}
```
If sending multiple documents in a single request, replace the input field with `["{{text}}", "{{..}}"]`:
```json
{
"source": "rest",
"request": {
"prompt": ["{{text}}", "{{..}}"]
…
},
…
}
```
This field is mandatory when using the `rest` embedder.
This field is incompatible with all other embedders.
##### `response`
`response` must be a JSON object with the same structure and data of the response you expect to receive from your `rest` embedder.
The field containing the embedding itself must be replaced with `"{{embedding}}"`:
```json
{
"source": "rest",
"response": {
"data": "{{embedding}}"
…
},
…
}
```
If a single response includes multiple embeddings, the field containing the embedding itself must be an array with two items. One must declare the location and structure of a single embedding, while the second item should be `"{{..}}"`:
```json
{
"source": "rest",
"response": {
"data": [
{
"embedding": "{{embedding}}"
},
"{{..}}"
]
…
},
…
}
```
This field is mandatory when using the `rest` embedder.
This field is incompatible with all other embedders.
##### `distribution`
For mathematical reasons, the `_rankingScore` of semantic search results tend to be closely grouped around an average value that depends on the embedder and model used. This may result in relevant semantic hits being underrepresented and irrelevant semantic hits being overrepresented compared with keyword search hits.
Use `distribution` when configuring an embedder to correct the returned `_rankingScore`s of the semantic hits with an affine transformation:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/indexes/INDEX_NAME/settings' \
-H 'Content-Type: application/json' \
--data-binary '{
"embedders": {
"default": {
"source": "huggingFace",
"model": "MODEL_NAME",
"distribution": {
"mean": 0.7,
"sigma": 0.3
}
}
}
}'
```
Configuring `distribution` requires a certain amount of trial and error, in which you must perform semantic searches and monitor the results. Based on their `rankingScore`s and relevancy, add the observed `mean` and `sigma` values for that index.
`distribution` is an optional field compatible with all embedder sources. It must be an object with two fields:
* `mean`: a number between `0` and `1` indicating the semantic score of "somewhat relevant" hits before using the `distribution` setting
* `sigma`: a number between `0` and `1` indicating the average absolute difference in `_rankingScore`s between "very relevant" hits and "somewhat relevant" hits, and "somewhat relevant" hits and "irrelevant hits".
Changing `distribution` does not trigger a reindexing operation.
##### `headers`
`headers` must be a JSON object whose keys represent the name of additional headers to send in requests to embedders, and whose values represent the value of these additional headers.
By default, Meilisearch sends the following headers with all requests to `rest` embedders:
* `Authorization: Bearer EMBEDDER_API_KEY` (only if `apiKey` was provided)
* `Content-Type: application/json`
If `headers` includes one of these fields, the explicitly declared values take precedence over the defaults.
This field is optional when using the `rest` embedder.
This field is incompatible with all other embedders.
##### `binaryQuantized`
When set to `true`, compresses vectors by representing each dimension with 1-bit values. This reduces the relevancy of semantic search results, but greatly reduces database size.
This option can be useful when working with large Meilisearch projects. Consider activating it if your project contains more than one million documents and uses models with more than 1400 dimensions.
**Activating `binaryQuantized` is irreversible.** Once enabled, Meilisearch converts all vectors and discards all vector data that does fit within 1-bit. The only way to recover the vectors' original values is to re-vectorize the whole index in a new embedder.
##### `pooling`
Configure how Meilisearch should merge individual tokens into a single embedding.
`pooling` must be one of the following strings:
* `"useModel"`: Meilisearch will fetch the pooling method from the model configuration. Default value for new embedders
* `"forceMean"`: always use mean pooling. Default value for embedders created in Meilisearch \<=v1.13
* `"forceCls"`: always use CLS pooling
If in doubt, use `"useModel"`. `"forceMean"` and `"forceCls"` are compatibility options that might be necessary for certain embedders and models.
`pooling` is optional for embedders with the `huggingFace` source.
`pooling` is invalid for all other embedder sources.
##### `indexingEmbedder` and `searchEmbedder`
When using a [composite embedder](#composite-embedders), configure separate embedders Meilisearch should use when vectorizing documents and search queries.
`indexingEmbedder` often benefits from the higher bandwidth and speed of remote providers so it can vectorize large batches of documents quickly. `searchEmbedder` may often benefits from the lower latency of processing queries locally.
Both fields must be an object and accept the same fields as a regular embedder, with the following exceptions:
* `indexingEmbedder` and `searchEmbedder` must use the same model for generating embeddings
* `indexingEmbedder` and `searchEmbedder` must have identical `dimension`s and `pooling` methods
* `source` is mandatory for both `indexingEmbedder` and `searchEmbedder`
* Neither sub-embedder can set `source` to `composite` or `userProvided`
* Neither `binaryQuantized` and `distribution` are valid sub-embedder fields and must always be declared in the main embedder
* `documentTemplate` and `documentTemplateMaxBytes` are invalid fields for `searchEmbedder`
* `documentTemplate` and `documentTemplateMaxBytes` are mandatory for `indexingEmbedder`, if applicable to its source
`indexingEmbedder` and `searchEmbedder` are mandatory when using the `composite` source.
`indexingEmbedder` and `searchEmbedder` are incompatible with all other embedder sources.
##### `indexingFragments`
This is an experimental feature. Use the Meilisearch Cloud UI or the experimental features endpoint to activate it:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{
"multimodal": true
}'
```
`indexingFragments` specifies which fields in your documents should be used to generate multimodal embeddings. It must be an object with the following structure:
```json
"FRAGMENT_NAME": {
"value": {
…
}
}
```
`FRAGMENT_NAME` can be any valid string. It must contain a single field, `value`. `value` must then follow your chosen model's specifications.
For example, for [VoyageAI's multimodal embedding route](https://docs.voyageai.com/reference/multimodal-embeddings-api), `value` must be an object containing a `content` field. `content` itself must contain an array of objects with a `type` field. Depending on `type`'s value, you must include either `text`, `image_url`, or `image_base64`:
```json
{
"VOYAGE_FRAGMENT_NAME_A": {
"value": {
"content": [
{
"type": "text",
"text": "A document called {{doc.title}} that can be described as {{doc.description}}"
}
]
}
},
"VOYAGE_FRAGMENT_NAME_B": {
"value": {
"content": [
{
"type": "image_url",
"image_url": "{{doc.image_url}}"
}
]
}
},
}
```
Use Liquid templates to interpolate document data into the fragment fields, where `doc` gives you access to all fields within a document.
`indexingFragments` is optional when using the `rest` source.
`indexingFragments` is incompatible with all other embedder sources.
Specifying a `documentTemplate` in an embedder using `indexingFragments` will result in an error.
You must specify at least one valid fragment in `searchFragments` when using `indexingFragments`.
##### `searchFragments`
This is an experimental feature. Use the Meilisearch Cloud UI or the experimental features endpoint to activate it:
```sh
curl \
-X PATCH 'MEILISEARCH_URL/experimental-features/' \
-H 'Content-Type: application/json' \
--data-binary '{
"multimodal": true
}'
```
`searchFragments` instructs Meilisearch how to parse fields present in a query's [`media` search parameter](/reference/api/search#media). It must be an object following the same structure as the [`indexingFragments`](/reference/api/settings#indexingfragments) object:
```json
"FRAGMENT_NAME": {
"value": {
…
}
}
```
As with `indexingFragments`, the content of `value` should follow your model's specification.
Use Liquid templates to interpolate search query data into the fragment fields, where `media` gives you access to all multimodal data received with a query:
```json
"SEARCH_FRAGMENT_A": {
"value": {
"content": [
{
"type": "image_base64",
"image_base64": "data:{{media.image.mime}};base64,{{media.image.data}}"
}
]
}
},
```
`searchFragments` is optional when using the `rest` source.
`searchFragments` is incompatible with all other embedder sources.
You must specify at least one valid fragment in `indexingFragments` when using `searchFragments`.
#### Example
```bash cURL
curl \
-X PATCH 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/embedders' \
-H 'Content-Type: application/json' \
--data-binary '{
"default": {
"source": "openAi",
"apiKey": "OPEN_AI_API_KEY",
"model": "text-embedding-3-small",
"documentTemplate": "A document titled '{{doc.title}}' whose description starts with {{doc.overview|truncatewords: 20}}"
}
}'
```
```javascript JS
client.index('INDEX_NAME').updateEmbedders({
default: {
source: 'openAi',
apiKey: 'OPEN_AI_API_KEY',
model: 'text-embedding-3-small',
documentTemplate: 'A document titled '{{doc.title}}' whose description starts with {{doc.overview|truncatewords: 20}}'
}
});
```
```php PHP
$client->updateEmbedders([
'default' => [
'source' => 'openAi',
'apiKey' => 'OPEN_AI_API_KEY',
'model' => 'text-embedding-3-small',
'documentTemplate' => 'A document titled '{{doc.title}}' whose description starts with {{doc.overview|truncatewords: 20}}'
]
]);
```
```ruby Ruby
client.index('INDEX_NAME').update_embedders(
default: {
source: 'openAi',
api_key: 'OPEN_AI_API_KEY',
model: 'text-embedding-3-small',
document_template: "A document titled '{{doc.title}}' whose description starts with {{doc.overview|truncatewords: 20}}"
}
)
```
```rust Rust
let embedders = HashMap::from([(
String::from("default"),
Embedder {
source: EmbedderSource::OpenAi,
api_key: Some(String::from("OPEN_AI_API_KEY")),
model: Some(String::from("text-embedding-3-small")),
document_template: Some(String::from("A document titled '{{doc.title}}' whose description starts with {{doc.overview|truncatewords: 20}}")),
..Embedder::default()
}
)]);
let task = index
.set_embedders(&embedders)
.await
.unwrap();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "kitchenware",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2024-05-11T09:33:12.691402Z"
}
```
You can use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
### Reset embedder settings
Removes all embedders from your index.
To remove a single embedder, use the [update embedder settings endpoint](#update-embedder-settings) and set the target embedder to `null`.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash cURL
curl \
-X DELETE 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/embedders'
```
```ruby Ruby
client.index('INDEX_NAME').reset_embedders
```
```rust Rust
index.reset_embedders().await.unwrap();
```
##### Response: `202 Accepted`
```json
{
"taskUid": 1,
"indexUid": "books",
"status": "enqueued",
"type": "settingsUpdate",
"enqueuedAt": "2022-04-14T20:53:32.863107Z"
}
```
You can use the returned `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task).
## Chat
This is an experimental feature. Use the Meilisearch Cloud UI or the experimental features endpoint to activate it:
```sh
curl \
-X PATCH 'http://localhost:7700/experimental-features/' \
-H 'Authorization: Bearer MEILISEARCH_API_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"chatCompletions": true
}'
```
The chat settings allow you to configure how your index integrates with Meilisearch's conversational search feature.
### Chat object
```json
{
"description": "A comprehensive movie database containing titles, overviews, genres, and release dates to help users find movies",
"documentTemplate": "{% for field in fields %}{% if field.is_searchable and field.value != nil %}{{ field.name }}: {{ field.value }}\n{% endif %}{% endfor %}",
"documentTemplateMaxBytes": 400,
"searchParameters": {
"hybrid": { "embedder": "my-embedder" },
"limit": 20
}
}
```
The chat object may contain the following fields:
| Name | Type | Default Value | Description |
| ------------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| **`description`** | String | Empty | The description of the index. Helps the LLM decide which index to use when generating answers |
| **`documentTemplate`** | String | `{% for field in fields %} {% if field.is_searchable and not field.value == nil %}{{ field.name }}: {{ field.value }} {% endif %} {% endfor %}` | Template defining the data Meilisearch sends to the LLM |
| **`documentTemplateMaxBytes`** | Integer | 400 | Maximum allowed size of rendered document template |
| **`searchParameters`** | Object | Empty | The search parameters to use when LLM is performing search requests |
### Search parameters object
Must be one of the following [search parameters](/reference/api/search#search-parameters-object):
* **`hybrid`**
* **`limit`**
* **`sort`**
* **`distinct`**
* **`matchingStrategy`**
* **`attributesToSearchOn`**
* **`rankingScoreThreshold`**
### Get index chat settings
Get the index chat settings configured for an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Example
```bash
curl \
-X GET 'http://localhost:7700/indexes/movies/settings/chat' \
-H 'Authorization: Bearer MEILISEARCH_KEY'
```
##### Response: `200 OK`
```json
{
"description": "",
"documentTemplate": "{% for field in fields %} {% if field.is_searchable and not field.value == nil %}{{ field.name }}: {{ field.value }} {% endif %} {% endfor %}",
"documentTemplateMaxBytes": 400,
"searchParameters": {}
}
```
### Update index chat settings
Partially update the index chat settings for an index.
#### Path parameters
| Name | Type | Description |
| :----------------- | :----- | :----------------------------------------------------------------------- |
| **`index_uid`** \* | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index |
#### Body
```json
{
"description": ,
"documentTemplate": ,
"documentTemplateMaxBytes": ,
"searchParameters": {
"hybrid":