# Meilisearch --- title: Getting started with Meilisearch Cloud — Meilisearch documentation description: Learn how to create your first Meilisearch Cloud project. --- ## Getting started with Meilisearch Cloud 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: ![The Meilisearch Cloud menu, featuring the "New Project" button](/assets/images/cloud-getting-started/1-new-project.png) Name your project `meilisearch-quick-start` and select the region closest to you, then click on "Create project": ![A modal window with two mandatory fields: "Project name" and "Select a region"](/assets/images/cloud-getting-started/2-create-project.png) 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: ![A variation of the previous modal window with an extra mandatory field: "Select a plan". There are a few billing plan options](/assets/images/cloud-getting-started/3-project-billing.png) 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: ![Meilisearch Cloud's main list of projects. It features only one project, "meilisearch-quick-start", and shows information such as API keys, URL, and number of monthly searches](/assets/images/cloud-getting-started/4-project-list.png) ### 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: ![The project overview page, featuring a secondary menu with several links. A red arrow points at a menu item: "Indexes"](/assets/images/cloud-getting-started/5-project-page-menu-indexes.png) This leads you to the index listing. Click on "New index": ![An empty list of indexes in this project with a button on the upper right corner](/assets/images/cloud-getting-started/6-index-list-empty.png) Write `movies` in the name field and click on "Create Index": ![A modal window with one mandatory field: "Index name"](/assets/images/cloud-getting-started/7-create-index.png) The final step in creating an index is to add data to it. Choose "File upload": ![Another modal window with three options. A red arrow points at the chosen option, "File upload"](/assets/images/cloud-getting-started/8-file-upload.png) Meilisearch Cloud will ask you for your dataset. To follow along with this tutorial, use this list of movies. Download the file to your computer, drag and drop it into the indicated area, then click on "Import documents": ![Another modal window with a large drag-and-drop area. It indicates a file named "movies.json" will be uploaded](/assets/images/cloud-getting-started/9-data-import.png) 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: ![A list of all indexes in this project. It shows a single index, `movies`, and indicates it contains over 30,000 documents](/assets/images/cloud-getting-started/10-index-list-filled.png) ### 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: ![The index list page. A red arrow points at the breadcrumb menu](/assets/images/cloud-getting-started/11-index-list-breadcrumb.png) Meilisearch Cloud comes with a search preview interface. Click on "Search preview" to access it: ![The project overview page. A red arrow points at a menu item named "Search preview"](/assets/images/cloud-getting-started/12-project-page-url.png) Finally, try searching for a few movies, like "Solaris": ![The search preview interface, with "solaris" written in the search bar](/assets/images/cloud-getting-started/13-search-preview.png) 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). --- title: What is Meilisearch? — Meilisearch documentation description: Meilisearch is a search engine featuring a blazing fast RESTful search API, typo tolerance, comprehensive language support, and much more. --- ## What is Meilisearch? 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 [![Search bar updating results](/assets/images/movies-demo-dark.gif)](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 - **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! --- title: Documents — Meilisearch documentation description: Documents are the individual items that make up a dataset. Each document is an object composed of one or more fields. sidebarDepth: 3 --- ## Documents 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 ![Diagram illustration Meilisearch's document structure](/assets/images/document_structure.svg) #### 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 consecutive operations into a single batch and processes them together. This significantly speeds up the indexing process. Meilisearch batches operations such as document addition and deletion when they: - Target the same index - Are immediately consecutive 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. --- title: Indexes — Meilisearch documentation description: An index is a collection of documents, much like a table in MySQL or a collection in MongoDB. sidebarDepth: 3 --- ## Indexes 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 `_`. **Once defined, the `uid` cannot be changed**, and you cannot create another index with the same `uid`. ```json { "uid": "movies", "createdAt": "2019-11-20T09:40:33.711324Z", "updatedAt": "2019-11-20T10:16:42.761858Z" } ``` ### 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. You can customize the following index settings: - [Displayed and searchable attributes](#displayed-and-searchable-attributes) - [Distinct attribute](#distinct-attribute) - [Faceting](#faceting) - [Filterable attributes](#filterable-attributes) - [Pagination](#pagination) - [Ranking rules](#ranking-rules) - [Sortable attributes](#sortable-attributes) - [Stop words](#stop-words) - [Synonyms](#synonyms) - [Typo tolerance](#typo-tolerance) 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). --- title: Primary key — Meilisearch documentation description: The primary key is a special field that must be present in all documents indexed by Meilisearch. sidebarDepth: 3 --- ## Primary key ### 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 \ -X POST 'MEILISEARCH_URL/indexes' \ -H 'Content-Type: application/json' \ --data-binary '{ "uid": "books", "primaryKey": "reference_number" }' ``` ```js client.createIndex('books', { primaryKey: 'reference_number' }) ``` ```py client.create_index('books', {'primaryKey': 'reference_number'}) ``` ```php $client->createIndex('books', ['primaryKey' => 'reference_number']); ``` ```java client.createIndex("books", "reference_number"); ``` ```ruby client.create_index('books', primary_key: 'reference_number') ``` ```go client.CreateIndex(&meilisearch.IndexConfig{ Uid: "books", PrimaryKey: "reference_number", }) ``` ```csharp TaskInfo task = await client.CreateIndexAsync("books", "reference_number"); ``` ```rust client .create_index("books", Some("reference_number")) .await .unwrap(); ``` ```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 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 \ -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 } ]' ``` ```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' }) ``` ```py 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 $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 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 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 documents := []map[string]interface{}{ { "reference_number": 287947, "title": "Diary of a Wimpy Kid", "author": "Jeff Kinney", "genres": []string{"comedy", "humor"}, "price": 5.00, }, } client.Index("books").AddDocuments(documents, "reference_number") ``` ```csharp 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 #[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 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 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 \ -X PATCH 'MEILISEARCH_URL/indexes/books' \ -H 'Content-Type: application/json' \ --data-binary '{ "primaryKey": "title" }' ``` ```js client.updateIndex('books', { primaryKey: 'title' }) ``` ```py client.index('books').update(primary_key='title') ``` ```php $client->updateIndex('books', ['primaryKey' => 'title']); ``` ```java client.updateIndex("books", "title"); ``` ```ruby client.index('books').update(primary_key: 'title') ``` ```go client.Index("books").UpdateIndex("title") ``` ```csharp TaskInfo task = await client.UpdateIndexAsync("books", "title"); ``` ```rust let task = IndexUpdater::new("books", &client) .with_primary_key("title") .execute() .await .unwrap(); ``` ```swift client.updateIndex(uid: "movies", primaryKey: "title") { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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" } ``` --- title: Search preview — Meilisearch documentation description: Meilisearch comes with a built-in search interface for quick testing during development. --- ## Search preview 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": ![Meilisearch Cloud's project menu with the last option, "Search preview", selected](/assets/images/search_preview/01-search-preview-menu.png) Select the index you want to search on using the input on the left-hand side: ![Meilisearch Cloud's search preview interface, with the index selecting input highlighted](/assets/images/search_preview/02-select-index.png) Then use the main input to perform plain keyword searches: ![Meilisearch Cloud's search preview interface, with the search input selected and containing a search string](/assets/images/search_preview/03-basic-search.png) 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: ![The same search preview interface as in the previous image, but with the "Ranking score" option turned on. Search results are the same, but include the document's ranking score](/assets/images/search_preview/04-score.png) ### 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: ![The sidebar of the search preview interface, with a handful of options, including "Sort by", "AI-powered search", "Filters", and "Genres"](/assets/images/search_preview/05-sidebar-options.png) 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: ![The same sidebar as before with the "Advanced parameters" option highlighted](/assets/images/search_preview/06-sidebar-options-advanced.png) ### 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: ![The search preview sidebar with a highlighted export button](/assets/images/search_preview/07-sidebar-options-export.png) --- title: Getting started with AI-powered search — Meilisearch documentation description: AI-powered search is an experimental technology that uses LLMs to retrieve search results. This tutorial shows you how to configure an OpenAI embedder and perform your first search. --- ## Getting started with AI-powered search [AI-powered search](https://meilisearch.com/solutions/vector-search), sometimes also called vector search or hybrid search, is an experimental technology that 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 activate this feature, generate document embeddings with OpenAI, 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](https://raw.githubusercontent.com/meilisearch/documentation/main/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. ### Activate AI-powered search AI-powered search is an experimental feature and is disabled by default. You must manually activate it either via the Meilisearch Cloud UI, or with the experimental features endpoint. To use AI-powered search with Meilisearch Cloud, you must first enter the waitlist. You will not be able to activate vector search until your sign-up has been approved. #### Meilisearch Cloud UI Navigate to your project overview and find "Experimental features". Then click on the "AI-powered search" box. ![A section of the project overview interface titled "Experimental features". The image shows a few options, including "Vector store".](https://raw.githubusercontent.com/meilisearch/documentation/main/assets/images/vector-search/01-cloud-vector-store.png) #### Experimental features endpoint Use [the `/experimental-features` route](/reference/api/experimental_features) to activate vector search during runtime: ```sh curl \ -X PATCH 'MEILISEARCH_URL/experimental-features/' \ -H 'Content-Type: application/json' \ --data-binary '{ "vectorStore": true }' ``` Replace `MEILISEARCH_URL` with your project's URL. In most cases, this should look like `https://ms-000xx00x000-xx.xxx.meilisearch.io` if you're using Meilisearch Cloud, or `http://localhost:7700` if you are running Meilisearch in your local machine. ### 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/ai/openai). There you will find specific instructions for embedders such as [LangChain](/guides/ai/langchain) and [Cloudflare](/guides/ai/cloudflare). For more in-depth information, consult the API reference for [embedder settings](/reference/api/settings#embedders-experimental) and [the `hybrid` search parameter](/reference/api/search#hybrid-search-experimental). --- title: Use AI-powered search with user-provided embeddings — Meilisearch documentation description: This guide shows how to perform AI-powered searches with user-generated embeddings instead of relying on a third-party tool. --- ## Use AI-powered 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. ### Requirements - A Meilisearch project with AI-powered search activated ### 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"] }' ``` --- title: Deactivate AI-powered search — Meilisearch documentation description: This guide shows how to disable Meilisearch's AI-powered search. --- ## Deactivate vector search This guide shows how to disable Meilisearch's AI-powered search. ### Remove embedder configuration Manually remove all embedder configuration from your index: If you don't remove all configured embedders, Meilisearch will continue auto-generating embeddings for you documents. This will happen even if `vectorStore` has been set to `false` and may lead to unexpected expenses when using OpenAI's paid tiers. ### Disable the vector store If using Meilisearch Cloud, navigate to your project overview and find "Experimental features", then uncheck the "Vector store" box. Alternatively, use [the `/experimental` route](/reference/api/experimental_features?utm_campaign=vector-search&utm_source=docs&utm_medium=vector-search-guide): ```sh curl \ -X PATCH 'MEILISEARCH_URL/experimental-features/' \ -H 'Content-Type: application/json' \ --data-binary '{ "vectorStore": false }' ``` --- title: Differences between full-text and AI-powered search — Meilisearch documentation description: "Meilisearch offers two types of search: full-text search and AI-powered search. This article explains their differences and intended use cases." --- ## Differences between full-text and AI-powered search 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. --- title: Getting started with self-hosted Meilisearch — Meilisearch documentation description: Learn how to install Meilisearch, index a dataset, and perform your first search. --- ## Getting started with self-hosted Meilisearch 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?utm_campaign=oss&utm_source=docs&utm_medium=quick-start). 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/documents?primaryKey=id' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer aSampleMasterKey' \ --data-binary @movies.json ``` ```bash npm install meilisearch ``` Or, if you are using `yarn` ```bash yarn add meilisearch ``` **Import** `require` syntax: ```js const { MeiliSearch } = require('meilisearch') const movies = require('./movies.json') ``` `import` syntax: ```js import { MeiliSearch } from 'meilisearch' import movies from './movies.json' ``` **Use** ```js const client = new MeiliSearch({ host: 'http://localhost:7700', apiKey: 'aSampleMasterKey' }) client.index('movies').addDocuments(movies) .then((res) => console.log(res)) ``` [About this SDK](https://github.com/meilisearch/meilisearch-js/) ```bash pip3 install meilisearch ``` ```python 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) ``` [About this SDK](https://github.com/meilisearch/meilisearch-python/) ```php Using `meilisearch-php` with the Guzzle HTTP client: ```bash composer require meilisearch/meilisearch-php \ guzzlehttp/guzzle \ http-interop/http-factory-guzzle:^1.0 ``` ```php index('movies')->addDocuments($movies); ``` [About this SDK](https://github.com/meilisearch/meilisearch-php/) ``` ```java **Maven** Add the following code to the `` section of your project: ```xml com.meilisearch.sdk meilisearch-java 0.14.2 pom ``` **Gradle** Add the following line to the `dependencies` section of your `build.gradle`: ```groovy implementation 'com.meilisearch.sdk:meilisearch-java:0.14.2' ``` ```java 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); ``` [About this SDK](https://github.com/meilisearch/meilisearch-java) ``` ```bash $ bundle add meilisearch ``` ```ruby 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) ``` [About this SDK](https://www.github.com/meilisearch/meilisearch-ruby) ```bash go get -u github.com/meilisearch/meilisearch-go ``` ```go 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) if err != nil { panic(err) } } ``` [About this SDK](https://github.com/meilisearch/meilisearch-go/) ```bash dotnet add package Meilisearch ``` ```csharp 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); } } } ``` [About this SDK](https://www.github.com/meilisearch/meilisearch-dotnet) ```toml [dependencies] meilisearch-sdk = "0.27.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" ``` Documents in the Rust library are strongly typed. ```rust #[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: ```rust #[derive(Serialize, Deserialize)] struct Movie { id: i64, #[serde(flatten)] value: serde_json::Value, } ``` Then, add documents into the index: ```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("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(); })} ``` [About this SDK](https://github.com/meilisearch/meilisearch-rust/) ```swift Add this to your `Package.swift`: ```swift dependencies: [ .package(url: "https://github.com/meilisearch/meilisearch-swift.git", from: "0.17.0") ] ``` ```swift 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) } } ``` [About this SDK](https://www.github.com/meilisearch/meilisearch-swift) ``` ```bash dart pub add meilisearch ``` ```dart 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); ``` [About this SDK](https://github.com/meilisearch/meilisearch-dart/) 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 \ -X GET 'MEILISEARCH_URL/tasks/0' \ -H 'Authorization: Bearer aSampleMasterKey' ``` ```js client.getTask(0) ``` ```py client.get_task(0) ``` ```php $client->getTask(0); ``` ```java client.getTask(0); ``` ```ruby client.task(0) ``` ```go client.GetTask(0) ``` ```csharp TaskInfo task = await client.GetTaskAsync(0); ``` ```rust client .get_task(0) .await .unwrap(); ``` ```swift client.getTask(taskUid: 0) { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer aSampleMasterKey' \ --data-binary '{ "q": "botman" }' ``` ```js client.index('movies').search('botman').then((res) => console.log(res)) ``` [About this SDK](https://github.com/meilisearch/meilisearch-js/) ```python client.index('movies').search('botman') ``` [About this SDK](https://github.com/meilisearch/meilisearch-python/) ```php $client->index('movies')->search('botman'); ``` [About this SDK](https://github.com/meilisearch/meilisearch-php/) ```java client.index("movies").search("botman"); ``` [About this SDK](https://github.com/meilisearch/meilisearch-java) ```ruby client.index('movies').search('botman') ``` [About this SDK](https://www.github.com/meilisearch/meilisearch-ruby) ```go client.Index("movies").Search("botman", &meilisearch.SearchRequest{}) ``` [About this SDK](https://github.com/meilisearch/meilisearch-go/) ```csharp 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); } ``` [About this SDK](https://www.github.com/meilisearch/meilisearch-dotnet) ```rust You can build a `SearchQuery` and execute it later: ```rust 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: ```rust let results: SearchResults = SearchQuery::new(&movies) .with_query("botman") .execute() .await .unwrap(); ``` You can search in an index directly: ```rust let results: SearchResults = client .index("movies") .search() .with_query("botman") .execute() .await .unwrap(); ``` [About this SDK](https://github.com/meilisearch/meilisearch-rust/) ``` ```swift client.index("movies").search(SearchParameters(query: "botman")) { (result) in switch result { case .success(let searchResult): print(searchResult) case .failure(let error): print(error) } } ``` [About this SDK](https://www.github.com/meilisearch/meilisearch-swift) ```dart await client.index('movies').search('botman'); ``` [About this SDK](https://github.com/meilisearch/meilisearch-dart/) 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). --- title: Configure Meilisearch at launch — Meilisearch documentation description: Configure Meilisearch at launch with command-line options, environment variables, or a configuration file. sidebarDepth: 3 --- ## Configure Meilisearch at launch 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/deployment/running_production#step-4-run-meilisearch-as-a-service). The default location of the service file is `/etc/systemd/system/meilisearch.service`. To configure Meilisearch with environment variables in a cloud-hosted instance, modify Meilisearch's `env` file. Its default location is `/var/opt/meilisearch/env`. After editing your configuration options, relaunch the Meilisearch service: ```sh 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). #### 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_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. #### 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/advanced/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. #### 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). #### 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. #### 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 #### 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. --- title: Install Meilisearch locally — Meilisearch documentation description: Use Meilisearch with either Meilisearch Cloud, another cloud service, or install it locally. --- ## Install Meilisearch 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.12 ## 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.12 ## Use ${pwd} instead of $(pwd) in PowerShell ``` You can learn more about [using Meilisearch with Docker in our dedicated guide](/guides/misc/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 ```
### Other cloud services To deploy Meilisearch on a third-party cloud service, follow one of our dedicated guides: - [AWS](/guides/deployment/aws) - [Azure](/guides/deployment/azure) - [DigitalOcean](/guides/deployment/digitalocean) - [Koyeb](/guides/deployment/koyeb) - [Qovery](/guides/deployment/qovery) - [Railway](/guides/deployment/railway) ### 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/misc/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 ```
--- title: Supported operating systems — Meilisearch documentation description: Meilisearch officially supports Windows, MacOS, and many Linux distributions. --- ## Supported operating systems 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.31 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. --- title: Configure search analytics description: Meilisearch Cloud offers in-depth search analytics to help you understand how users search in your application. --- ## Configure search analytics 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: ![The analytics section of the project overview. It shows one button, "Enable analytics", and a short explanation of the feature.](/assets/images/cloud-analytics/01-analytics-enable.png) 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/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 \ -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 \ -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). --- title: Configure application monitoring metrics description: Meilisearch Cloud offers in-depth metrics to help monitor your application performance. --- ## Configure application monitoring metrics 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: ![The analytics and monitoring section of the project overview. It shows one button, "Enable analytics and monitoring", and a short explanation of both features.](/assets/images/cloud-analytics/01-analytics-enable.png) 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/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. --- title: Bind search analytics events to a user description: This guide shows you how to manually differentiate users across search analytics using the X-MS-USER-ID HTTP header. --- ## Bind search analytics events to a user 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 \ -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 \ -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. --- title: Deactivate search analytics and monitoring description: Meilisearch Cloud offers in-depth search analytics to help you understand how users search in your application. --- ## Deactivate search analytics and monitoring 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: ![The analytics section of the project overview. It shows one button, "Disable analytics and monitoring", and a short explanation of both features.](/assets/images/cloud-analytics/02-analytics-disable.png) ### 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. --- title: Analytics events endpoint description: 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. --- ## 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. ### The `/events` endpoint The `/events` endpoint is only available for Meilisearch Cloud projects with analytics and monitoring activated. #### Send an event 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 ```bash 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 }' ``` ###### 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 --- title: Meilisearch Cloud teams — Meilisearch documentation description: Meilisearch Cloud teams helps collaboration between project stakeholders with different skillsets and responsibilities. --- ## Meilisearch Cloud teams 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. --- title: Working with tasks — Meilisearch documentation description: In this tutorial, you'll use the Meilisearch API to add documents to an index, and then monitor its status. --- ## Working with tasks [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 \ -X POST 'MEILISEARCH_URL/indexes/movies/documents'\ -H 'Content-Type: application/json' \ --data-binary @movies.json ``` ```js const movies = require('./movies.json') client.index('movies').addDocuments(movies).then((res) => console.log(res)) ``` ```py import json json_file = open('movies.json', encoding='utf-8') movies = json.load(json_file) client.index('movies').add_documents(movies) ``` ```php $moviesJson = file_get_contents('movies.json'); $movies = json_decode($moviesJson); $client->index('movies')->addDocuments($movies); ``` ```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 require 'json' movies_json = File.read('movies.json') movies = JSON.parse(movies_json) client.index('movies').add_documents(movies) ``` ```go import ( "encoding/json" "os" ) file, _ := os.ReadFile("movies.json") var movies interface{} json.Unmarshal([]byte(file), &movies) client.Index("movies").AddDocuments(&movies) ``` ```csharp // 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 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 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 // 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: ![](https://raw.githubusercontent.com/meilisearch/documentation/main/assets/images/cloud-tasks-tutorial/01-tasks-menu.png) This will lead you to the task overview. Look for your request's `taskUid` in the "Uid" column: ![](https://raw.githubusercontent.com/meilisearch/documentation/main/assets/images/cloud-tasks-tutorial/01-tasks-table.png) 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 \ -X GET 'MEILISEARCH_URL/tasks/1' ``` ```js client.getTask(1) ``` ```py client.get_task(1) ``` ```php $client->getTask(1); ``` ```java client.getTask(1); ``` ```ruby client.task(1) ``` ```go client.GetTask(1); ``` ```csharp TaskInfo task = await client.GetTaskAsync(1); ``` ```rust let task: Task = client .get_task(1) .await .unwrap(); ``` ```swift client.getTask(taskUid: 1) { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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. If you are working with a self-hosted Meilisearch instance, you may also [set up a webhook listener](/learn/async/task_webhook). 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. --- title: Filtering tasks — Meilisearch documentation description: This guide shows you how to use query parameters to filter tasks and obtain a more readable list of asynchronous operations. --- ## Filtering tasks 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 \ -X GET 'MEILISEARCH_URL/tasks?statuses=failed' ``` ```js client.getTasks({ statuses: ['failed', 'canceled'] }) ``` ```py client.get_tasks({'statuses': ['failed', 'canceled']}) ``` ```php $client->getTasks((new TasksQuery())->setStatuses(['failed', 'canceled'])); ``` ```java TasksQuery query = new TasksQuery().setStatuses(new String[] {"failed", "canceled"}); client.getTasks(query); ``` ```ruby client.get_tasks(statuses: ['failed', 'canceled']) ``` ```go client.GetTasks(&meilisearch.TasksQuery{ Statuses: []meilisearch.TaskStatus{ meilisearch.TaskStatusFailed, meilisearch.TaskStatusCanceled, }, }) ``` ```csharp await client.GetTasksAsync(new TasksQuery { Statuses = new List { TaskInfoStatus.Failed, TaskInfoStatus.Canceled } }); ``` ```rust let mut query = TasksQuery::new(&client); let tasks = query .with_statuses(["failed", "canceled"]) .execute() .await .unwrap(); ``` ```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 await client.getTasks( params: TasksQuery( statuses: ['failed', 'canceled'], ), ); ``` Use a comma to separate multiple values and fetch both `canceled` and `failed` tasks: ```bash curl \ -X GET 'MEILISEARCH_URL/tasks?statuses=failed,canceled' ``` 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 \ -X GET 'MEILISEARCH_URL/tasks?indexUids=movies&types=documentAdditionOrUpdate,documentDeletion&statuses=processing' ``` ```js client.getTasks({ indexUids: ['movies'], types: ['documentAdditionOrUpdate','documentDeletion'], statuses: ['processing'] }) ``` ```py client.get_tasks( { 'indexUids': 'movies', 'types': ['documentAdditionOrUpdate', 'documentDeletion'], 'statuses': ['processing'], } ) ``` ```php $client->getTasks( (new TasksQuery()) ->setStatuses(['processing']) ->setUids(['movies']) ->setTypes(['documentAdditionOrUpdate', 'documentDeletion']) ); ``` ```java TasksQuery query = new TasksQuery() .setStatuses(new String[] {"processing"}) .setTypes(new String[] {"documentAdditionOrUpdate", "documentDeletion"}) .setIndexUids(new String[] {"movies"}); client.getTasks(query); ``` ```ruby client.get_tasks(index_uids: ['movies'], types: ['documentAdditionOrUpdate', 'documentDeletion'], statuses: ['processing']) ``` ```go client.GetTasks(&meilisearch.TasksQuery{ IndexUIDS: []string{"movie"}, Types: []meilisearch.TaskType{ meilisearch.TaskTypeDocumentAdditionOrUpdate, meilisearch.TaskTypeDocumentDeletion, }, Statuses: []meilisearch.TaskStatus{ meilisearch.TaskStatusProcessing, }, }) ``` ```csharp 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 let mut query = TasksQuery::new(&client); let tasks = query .with_index_uids(["movies"]) .with_types(["documentAdditionOrUpdate","documentDeletion"]) .with_statuses(["processing"]) .execute() .await .unwrap(); ``` ```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 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`. --- title: Managing the task database — Meilisearch documentation description: Meilisearch uses a task queue to handle asynchronous operations. This document describes how to navigate long task queues with filters and pagination. --- ## Paginating tasks 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 \ -X GET 'MEILISEARCH_URL/tasks?limit=2&from=10 ``` ```js client.getTasks({ limit: 2, from: 10 }) ``` ```py client.get_tasks({ 'limit': 2, 'from': 10 }) ``` ```php $taskQuery = (new TasksQuery())->setLimit(2)->setFrom(10)); $client->getTasks($taskQuery); ``` ```java TasksQuery query = new TasksQuery() .setLimit(2) .setFrom(10); client.index("movies").getTasks(query); ``` ```ruby client.tasks(limit: 2, from: 10) ``` ```go client.GetTasks(&meilisearch.TasksQuery{ Limit: 2, From: 10, }); ``` ```csharp ResourceResults taskResult = await client.GetTasksAsync(new TasksQuery { Limit = 2, From = 10 }); ``` ```rust let mut query = TasksSearchQuery::new(&client) .with_limit(2) .with_from(10) .execute() .await .unwrap(); ``` ```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 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 \ -X GET 'MEILISEARCH_URL/tasks?limit=2&from=8 ``` ```js client.getTasks({ limit: 2, from: 8 }) ``` ```py client.get_tasks({ 'limit': 2, 'from': 8 }) ``` ```php $taskQuery = (new TasksQuery())->setLimit(2)->setFrom(8)); $client->getTasks($taskQuery); ``` ```java TasksQuery query = new TasksQuery() .setLimit(2) .setFrom(8); client.index("movies").getTasks(query); ``` ```ruby client.tasks(limit: 2, from: 8) ``` ```go client.GetTasks(&meilisearch.TasksQuery{ Limit: 2, From: 8, }); ``` ```csharp ResourceResults taskResult = await client.GetTasksAsync(new TasksQuery { Limit = 2, From = 8 }); ``` ```rust let mut query = TasksSearchQuery::new(&client) .with_limit(2) .from(8) .execute() .await .unwrap(); ``` ```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 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. --- title: Using task webhooks — Meilisearch documentation description: Learn how to use webhooks to react to changes in your Meilisearch database. sidebarDepth: 3 --- ## Using task webhooks This guide teaches you how to use webhooks to notify a URL when Meilisearch completed a [task](/learn/async/asynchronous_operations). ### 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"} ``` --- title: Tasks and asynchronous operations — Meilisearch documentation description: 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. sidebarDepth: 3 --- ## Tasks and asynchronous operations 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](/learn/self_hosted/configure_meilisearch_at_launch#task-webhook-url) 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. --- title: Configuring index settings — Meilisearch documentation description: This tutorial shows how to check and change an index setting using the Meilisearch Cloud interface. --- ## Configuring index settings with 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": ![The main menu of the project view in the Meilisearch Cloud interface. Menu items include "Indexes" among other options such as "Settings" and "Analytics".](/assets/images/cloud-index-settings/01-indexes-tab.png) Find the index you want to configure and click on its "Settings" button: ![A list of indexes in a Meilisearch Cloud project. It shows an index named "books" along with a few icons and buttons. One of these buttons is "Settings."](/assets/images/cloud-index-settings/02-index-settings.png) ### Checking a setting's current value Using the menu on the left-hand side, click on "Attributes": ![The index configuration overview together with a menu with links to pages dedicated to various index settings.](/assets/images/cloud-index-settings/03-general-settings.png) The first setting is "Searchable attributes" and lists all attributes in your dataset's documents: ![The "Searchable attributes" configuration section showing six attributes. One of them, "id" is this index's primary key.](/assets/images/cloud-index-settings/04-searchable-attributes-default.png) 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: ![The same "Searchable attributes" list as before, with the bin-shaped "delete" icon highlighted.](/assets/images/cloud-index-settings/05-searchable-attributes-delete.png) 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": ![A pop-up window over the index settings interface. It reads: "Are you sure you want to remove the attribute id?" Below it are two buttons: "Cancel" and "Yes, remove attribute".](/assets/images/cloud-index-settings/06-searchable-attributes-confirm-deletion.png) 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: ![The same "Searchable attributes" list as before. It only contains five searchable attributes after removing the primary key.](/assets/images/cloud-index-settings/07-searchable-attributes-attribute-deleted.png) 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: ![The same "Searchable attributes" list as before. Two buttons on its top-right corner are highlighted: "Reset to default" and "Add attributes".](/assets/images/cloud-index-settings/08-searchable-attributes-reset.png) ### 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). --- title: Configuring index settings with the Meilisearch API — Meilisearch documentation description: This tutorial shows how to check and change an index setting using the Meilisearch API. --- ## Configuring index settings with 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/?utm_campaign=oss&utm_source=docs&utm_medium=settings-api-tutorial) 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 \ -X GET 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/searchable-attributes' ``` 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 \ -X PUT 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/searchable-attributes' \ -H 'Content-Type: application/json' \ --data-binary '[ "title", "overview" ]' ``` 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 \ -X GET 'MEILISEARCH_URL/tasks/TASK_UID' ``` 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 \ -X GET 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/searchable-attributes' ``` 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). --- title: Filter search results — Meilisearch documentation description: In this guide you will see how to configure and use Meilisearch filters in a hypothetical movie database. --- ## Filter search results 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 \ -X PUT 'MEILISEARCH_URL/indexes/movie_ratings/settings/filterable-attributes' \ -H 'Content-Type: application/json' \ --data-binary '[ "genres", "director", "release_date", "ratings" ]' ``` ```js client.index('movies') .updateFilterableAttributes([ 'director', 'genres' ]) ``` ```py client.index('movies').update_filterable_attributes([ 'director', 'genres', ]) ``` ```php $client->index('movies')->updateFilterableAttributes(['director', 'genres']); ``` ```java client.index("movies").updateFilterableAttributesSettings(new String[] { "genres", "director" }); ``` ```ruby client.index('movies').update_filterable_attributes([ 'director', 'genres' ]) ``` ```go resp, err := client.Index("movies").UpdateFilterableAttributes(&[]string{ "director", "genres", }) ``` ```csharp await client.Index("movies").UpdateFilterableAttributesAsync(new [] { "director", "genres" }); ``` ```rust let task: TaskInfo = client .index("movies") .set_filterable_attributes(["director", "genres"]) .await .unwrap(); ``` ```swift client.index("movies").updateFilterableAttributes(["genre", "director"]) { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X POST 'MEILISEARCH_URL/indexes/movie_ratings/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "Avengers", "filter": "release_date > 795484800" }' ``` ```js client.index('movie_ratings').search('Avengers', { filter: 'release_date > 795484800' }) ``` ```py client.index('movie_ratings').search('Avengers', { 'filter': 'release_date > 795484800' }) ``` ```php $client->index('movie_ratings')->search('Avengers', [ 'filter' => 'release_date > 795484800' ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("Avengers").filter(new String[] {"release_date > \"795484800\""}).build(); client.index("movie_ratings").search(searchRequest); ``` ```ruby client.index('movie_ratings').search('Avengers', { filter: 'release_date > 795484800' }) ``` ```go resp, err := client.Index("movie_ratings").Search("Avengers", &meilisearch.SearchRequest{ Filter: "release_date > \"795484800\"", }) ``` ```csharp SearchQuery filters = new SearchQuery() { Filter = "release_date > \"795484800\"" }; var movies = await client.Index("movie_ratings").SearchAsync("Avengers", filters); ``` ```rust let results: SearchResults = client .index("movie_ratings") .search() .with_query("Avengers") .with_filter("release_date > 795484800") .execute() .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/movie_ratings/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "thriller", "filter": "rating.users >= 90" }' ``` ```js client.index('movie_ratings').search('thriller', { filter: 'rating.users >= 90' }) ``` ```py client.index('movie_ratings').search('thriller', { 'filter': 'rating.users >= 90' }) ``` ```php $client->index('movie_ratings')->search('thriller', [ 'filter' => 'rating.users >= 90' ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("thriller").filter(new String[] {"rating.users >= 90"}).build(); client.index("movie_ratings").search(searchRequest); ``` ```ruby client.index('movies_ratings').search('thriller', { filter: 'rating.users >= 90' }) ``` ```go resp, err := client.Index("movie_ratings").Search("thriller", &meilisearch.SearchRequest{ Filter: "rating.users >= 90", }) ``` ```csharp var filters = new SearchQuery() { Filter = "rating.users >= 90" }; var movies = await client.Index("movie_ratings").SearchAsync("thriller", filters); ``` ```rust let results: SearchResults = client .index("movie_rating") .search() .with_query("thriller") .with_filter("rating.users >= 90") .execute() .await .unwrap(); ``` ```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 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 \ -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\")" }' ``` ```js client.index('movie_ratings').search('Batman', { filter: 'release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")' }) ``` ```py client.index('movie_ratings').search('Batman', { 'filter': 'release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")' }) ``` ```php $client->index('movie_ratings')->search('Batman', [ 'filter' => 'release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")' ]); ``` ```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 client.index('movie_ratings').search('Batman', { filter: 'release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")' }) ``` ```go resp, err := client.Index("movie_ratings").Search("Batman", &meilisearch.SearchRequest{ Filter: "release_date > 795484800 AND (director = \"Tim Burton\" OR director = \"Christopher Nolan\")", }) ``` ```csharp 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 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 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 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 \ -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\")" }' \ ``` ```js client.index('movie_ratings').search('Planet of the Apes', { filter: "release_date > 1577884550 AND (NOT director = \"Tim Burton\")" }) ``` ```py client.index('movie_ratings').search('Planet of the Apes', { 'filter': 'release_date > 1577884550 AND (NOT director = "Tim Burton"))' }) ``` ```php $client->index('movie_ratings')->search('Planet of the Apes', [ 'filter' => 'release_date > 1577884550 AND (NOT director = "Tim Burton")' ]); ``` ```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 client.index('movie_ratings').search('Planet of the Apes', { filter: "release_date > 1577884550 AND (NOT director = \"Tim Burton\")" }) ``` ```go resp, err := client.Index("movie_ratings").Search("Planet of the Apes", &meilisearch.SearchRequest{ Filter: "release_date > 1577884550 AND (NOT director = \"Tim Burton\")", }) ``` ```csharp 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 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 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 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()), ), ]), ), ); ``` `NOT director = "Tim Burton"` will include both documents that do not contain `"Tim Burton"` in its `director` field and documents without a `director` field. To return only documents that have a `director` field, expand the filter expression with the `EXISTS` operator: ``` 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. --- title: Search with facets — Meilisearch documentation description: Faceted search interfaces provide users with a quick way to narrow down search results by selecting categories relevant to their query. --- ## Search with facets 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 \ -X PUT 'MEILISEARCH_URL/indexes/books/settings/filterable-attributes' \ -H 'Content-Type: application/json' \ --data-binary '[ "genres", "rating", "language" ]' ``` ```js client.index('movie_ratings').updateFilterableAttributes(['genres', 'rating', 'language']) ``` ```py client.index('movie_ratings').update_filterable_attributes([ 'genres', 'director', 'language' ]) ``` ```php $client->index('movie_ratings')->updateFilterableAttributes(['genres', 'rating', 'language']); ``` ```java client.index("movie_ratings").updateFilterableAttributesSettings(new String[] { "genres", "director", "language" }); ``` ```ruby client.index('movie_ratings').update_filterable_attributes(['genres', 'rating', 'language']) ``` ```go filterableAttributes := []string{ "genres", "rating", "language", } client.Index("movie_ratings").UpdateFilterableAttributes(&filterableAttributes) ``` ```csharp List attributes = new() { "genres", "rating", "language" }; TaskInfo result = await client.Index("movie_ratings").UpdateFilterableAttributesAsync(attributes); ``` ```rust let task: TaskInfo = client .index("movie_ratings") .set_filterable_attributes(&["genres", "rating", "language"]) .await .unwrap(); ``` ```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 \ -X POST 'MEILISEARCH_URL/indexes/books/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "classic", "facets": [ "genres", "rating", "language" ] }' ``` ```js client.index('books').search('classic', { facets: ['genres', 'rating', 'language'] }) ``` ```py client.index('books').search('classic', { 'facets': ['genres', 'rating', 'language'] }) ``` ```php $client->index('books')->search('classic', [ 'facets' => ['genres', 'rating', 'language'] ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("classic").facets(new String[] { "genres", "rating", "language" }).build(); client.index("books").search(searchRequest); ``` ```ruby client.index('books').search('classic', { facets: ['genres', 'rating', 'language'] }) ``` ```go resp, err := client.Index("books").Search("classic", &meilisearch.SearchRequest{ Facets: []string{ "genres", "rating", "language", }, }) ``` ```csharp var sq = new SearchQuery { Facets = new string[] { "genres", "rating", "language" } }; await client.Index("books").SearchAsync("classic", sq); ``` ```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 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 \ -X PATCH 'MEILISEARCH_URL/indexes/books/settings/faceting' \ -H 'Content-Type: application/json' \ --data-binary '{ "sortFacetValuesBy": { "genres": "count" } }' ``` ```js client.index('books').updateFaceting({ sortFacetValuesBy: { genres: 'count' } }) ``` ```py client.index('books').update_faceting_settings({ 'sortFacetValuesBy': { 'genres': 'count' } }) ``` ```php $client->index('books')->updateFaceting(['sortFacetValuesBy' => ['genres' => 'count']]); ``` ```java Faceting newFaceting = new Faceting(); HashMap facetSortValues = new HashMap<>(); facetSortValues.put("genres", FacetSortValue.COUNT); newFaceting.setSortFacetValuesBy(facetSortValues); client.index("books").updateFacetingSettings(newFaceting); ``` ```ruby client.index('books').update_faceting( sort_facet_values_by: { genres: 'count' } ) ``` ```go client.Index("books").UpdateFaceting(&meilisearch.Faceting{ SortFacetValuesBy: { "genres": SortFacetTypeCount, } }) ``` ```csharp var newFaceting = new Faceting { SortFacetValuesBy = new Dictionary { ["genres"] = SortFacetValuesByType.Count } }; await client.Index("books").UpdateFacetingAsync(newFaceting); ``` ```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 \ -X POST 'MEILISEARCH_URL/indexes/books/facet-search' \ -H 'Content-Type: application/json' \ --data-binary '{ "facetQuery": "c", "facetName": "genres" }' ``` ```js client.index('books').searchForFacetValues({ facetQuery: 'c', facetName: 'genres' }) ``` ```py client.index('books').facet_search('genres', 'c') ``` ```php $client->index('books')->facetSearch( (new FacetSearchQuery()) ->setFacetQuery('c') ->setFacetName('genres') ); ``` ```java FacetSearchRequest fsr = FacetSearchRequest.builder().facetName("genres").facetQuery("c").build(); client.index("books").facetSearch(fsr); ``` ```ruby client.index('books').facet_search('genres', 'c') ``` ```go client.Index("books").FacetSearch(&meilisearch.FacetSearchRequest{ FacetQuery: "c", FacetName: "genres", }) ``` ```csharp var query = new SearchFacetsQuery() { FacetQuery = "c" }; await client.Index("books").FacetSearchAsync("genres", query); ``` ```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) --- title: Filtering and sorting by date — Meilisearch documentation description: Learn how to index documents with chronological data, and how to sort and filter search results based on time. --- ## Filtering and sorting by date 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 numeric field containing a [UNIX timestamp](https://kb.narrative.io/what-is-unix-time). 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 } ] ``` If your date field is expressed in a format other than a numeric timestamp, like [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html), you must convert it before indexing it with Meilisearch. Most programming languages have built-in tools to help you with this process. The JavaScript example below converts a game's release date, `"2018-10-18"`, to a numeric timestamp: ```js let game = { "id": 0, "title": "Return of the Obra Dinn", "genre": "adventure", "release_date": "2018-10-18T00:00Z" }; const timestampInMilliseconds = Date.parse(game.release_date); // Date.parse returns the timestamp in milliseconds const timestamp = timestampInMilliseconds / 1000; // UNIX timestamps must be in seconds game = { "id": 0, "title": "Return of the Obra Dinn", "genre": "adventure", "release_date": "2018-10-18T00:00Z", "release_timestamp": timestamp }; ``` When preparing your dataset, it can be useful to leave the original date and time fields in your documents intact. In the example above, we keep the `release_date` field because it is more readable than the raw `release_timestamp`. After adding a numeric timestamp to all documents, [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 \ -x POST 'MEILISEARCH_URL/indexes/games/documents' \ -h 'content-type: application/json' \ --data-binary @games.json ``` ```js const games = require('./games.json') client.index('games').addDocuments(games).then((res) => console.log(res)) ``` ```py import json json_file = open('./games.json', encoding='utf-8') games = json.load(json_file) client.index('games').add_documents(games) ``` ```php $gamesJson = file_get_contents('games.json'); $games = json_decode($gamesJson); $client->index('games')->addDocuments($games); ``` ```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 require 'json' games = JSON.parse(File.read('games.json')) client.index('games').add_documents(games) ``` ```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) ``` ```csharp string jsonString = await File.ReadAllTextAsync("games.json"); var games = JsonSerializer.Deserialize>(jsonString, options); var index = client.Index("games"); await index.AddDocumentsAsync(games); ``` ```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 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 //import 'dart:io'; //import 'dart:convert'; final json = await File('games.json').readAsString(); await client.index('games').addDocumentsJson(json); ``` ### Filtering by timestamp 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 \ -X PUT 'MEILISEARCH_URL/indexes/games/settings/filterable-attributes' \ -H 'Content-Type: application/json' \ --data-binary '[ "release_timestamp" ]' ``` ```js client.index('games').updateFilterableAttributes(['release_timestamp']) ``` ```py client.index('games').update_filterable_attributes(['release_timestamp']) ``` ```php $client->index('games')->updateFilterableAttributes(['release_timestamp']); ``` ```java client.index("movies").updateFilterableAttributesSettings(new String[] { "release_timestamp" }); ``` ```ruby client.index('games').update_filterable_attributes(['release_timestamp']) ``` ```go filterableAttributes := []string{"release_timestamp"} client.Index("games").UpdateFilterableAttributes(&filterableAttributes) ``` ```csharp await client.Index("games").UpdateFilterableAttributesAsync(new string[] { "release_timestamp" }); ``` ```rust let settings = Settings::new() .with_filterable_attributes(["release_timestamp"]); let task: TaskInfo = client .index("games") .set_settings(&settings) .await .unwrap(); ``` ```swift client.index("games").updateFilterableAttributes(["release_timestamp"]) { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X POST 'MEILISEARCH_URL/indexes/games/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "", "filter": "release_timestamp >= 1514761200 AND release_timestamp < 1672527600" }' ``` ```js client.index('games').search('', { filter: 'release_timestamp >= 1514761200 AND release_timestamp < 1672527600' }) ``` ```py client.index('games').search('', { 'filter': 'release_timestamp >= 1514761200 AND release_timestamp < 1672527600' }) ``` ```php $client->index('games')->search('', [ 'filter' => ['release_timestamp >= 1514761200 AND release_timestamp < 1672527600'] ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("").filter(new String[] {"release_timestamp >= 1514761200 AND release_timestamp < 1672527600"}).build(); client.index("games").search(searchRequest); ``` ```ruby client.index('games').search('', { filter: 'release_timestamp >= 1514761200 AND release_timestamp < 1672527600' }) ``` ```go client.Index("games").Search("", &meilisearch.SearchRequest{ Filter: "release_timestamp >= 1514761200 AND release_timestamp < 1672527600", }) ``` ```csharp var filters = new SearchQuery() { Filter = "release_timestamp >= 1514761200 AND release_timestamp < 1672527600" }; var games = await client.Index("games").SearchAsync("", filters); ``` ```rust let results: SearchResults = client .index("games") .search() .with_filter("release_timestamp >= 1514761200 AND release_timestamp < 1672527600") .execute() .await .unwrap(); ``` ```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 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 timestamp To sort search results chronologically, add your document's timestamp field to the list of [`sortableAttributes`](/reference/api/settings#update-sortable-attributes): ```bash curl \ -X PUT 'MEILISEARCH_URL/indexes/games/settings/sortable-attributes' \ -H 'Content-Type: application/json' \ --data-binary '[ "release_timestamp" ]' ``` ```js client.index('games').updateSortableAttributes(['release_timestamp']) ``` ```py client.index('games').update_sortable_attributes(['release_timestamp']) ``` ```php $client->index('games')->updateSortableAttributes(['release_timestamp']); ``` ```java Settings settings = new Settings(); settings.setSortableAttributes(new String[] {"release_timestamp"}); client.index("games").updateSettings(settings); ``` ```ruby client.index('games').update_sortable_attributes(['release_timestamp']) ``` ```go sortableAttributes := []string{"release_timestamp","author"} client.Index("games").UpdateSortableAttributes(&sortableAttributes) ``` ```csharp await client.Index("games").UpdateSortableAttributesAsync(new string[] { "release_timestamp" }); ``` ```rust let settings = Settings::new() .with_sortable_attributes(["release_timestamp"]); let task: TaskInfo = client .index("games") .set_settings(&settings) .await .unwrap(); ``` ```swift client.index("games").updateSortableAttributes(["release_timestamp"]) { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X POST 'MEILISEARCH_URL/indexes/games/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "", "sort": ["release_timestamp:desc"] }' ``` ```js client.index('games').search('', { sort: ['release_timestamp:desc'], }) ``` ```py client.index('games').search('', { 'sort': ['release_timestamp:desc'] }) ``` ```php $client->index('games')->search('', ['sort' => ['release_timestamp:desc']]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("").sort(new String[] {"release_timestamp:desc"}).build(); client.index("games").search(searchRequest); ``` ```ruby client.index('games').search('', sort: ['release_timestamp:desc']) ``` ```go client.Index("games").Search("", &meilisearch.SearchRequest{ Sort: []string{ "release_timestamp:desc", }, }) ``` ```csharp SearchQuery sort = new SearchQuery() { Sort = new string[] { "release_timestamp:desc" }}; await client.Index("games").SearchAsync("", sort); ``` ```rust let results: SearchResults = client .index("games") .search() .with_sort(["release_timestamp:desc"]) .execute() .await .unwrap(); ``` ```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 await client .index('games') .search('', SearchQuery(sort: ['release_timestamp:desc'])); ``` --- title: Sort search results — Meilisearch documentation description: 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. sidebarDepth: 3 --- ## Sort search results 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 \ -X PUT 'MEILISEARCH_URL/indexes/books/settings/sortable-attributes' \ -H 'Content-Type: application/json' \ --data-binary '[ "author", "price" ]' ``` ```js client.index('books').updateSortableAttributes([ 'author', 'price' ]) ``` ```py client.index('books').update_sortable_attributes([ 'author', 'price' ]) ``` ```php $client->index('books')->updateSortableAttributes([ 'author', 'price' ]); ``` ```java client.index("books").updateSortableAttributesSettings(new String[] {"price", "author"}); ``` ```ruby client.index('books').update_sortable_attributes(['author', 'price']) ``` ```go sortableAttributes := []string{ "author", "price", } client.Index("books").UpdateSortableAttributes(&sortableAttributes) ``` ```csharp await client.Index("books").UpdateSortableAttributesAsync(new [] { "price", "author" }); ``` ```rust let sortable_attributes = [ "author", "price" ]; let task: TaskInfo = client .index("books") .set_sortable_attributes(&sortable_attributes) .await .unwrap(); ``` ```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 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 \ -X PUT 'MEILISEARCH_URL/indexes/books/settings/ranking-rules' \ -H 'Content-Type: application/json' \ --data-binary '[ "words", "sort", "typo", "proximity", "attribute", "exactness" ]' ``` ```js client.index('books').updateRankingRules([ 'words', 'sort', 'typo', 'proximity', 'attribute', 'exactness' ]) ``` ```py client.index('books').update_ranking_rules([ 'words', 'sort', 'typo', 'proximity', 'attribute', 'exactness' ]) ``` ```php $client->index('books')->updateRankingRules([ 'words', 'sort', 'typo', 'proximity', 'attribute', 'exactness' ]); ``` ```java Settings settings = new Settings(); settings.setRankingRules(new String[] { "words", "sort", "typo", "proximity", "attribute", "exactness" }); client.index("books").updateSettings(settings); ``` ```ruby client.index('books').update_ranking_rules([ 'words', 'sort', 'typo', 'proximity', 'attribute', 'exactness' ]) ``` ```go rankingRules := []string{ "words", "sort", "typo", "proximity", "attribute", "exactness", } client.Index("books").UpdateRankingRules(&rankingRules) ``` ```csharp await client.Index("books").UpdateRankingRulesAsync(new[] { "words", "sort", "typo", "proximity", "attribute", "exactness" }); ``` ```rust let ranking_rules = [ "words", "sort", "typo", "proximity", "attribute", "exactness" ]; let task: TaskInfo = client .index("books") .set_ranking_rules(&ranking_rules) .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/books/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "science fiction", "sort": ["price:asc"] }' ``` ```js client.index('books').search('science fiction', { sort: ['price:asc'], }) ``` ```py client.index('books').search('science fiction', { 'sort': ['price:asc'] }) ``` ```php $client->index('books')->search('science fiction', ['sort' => ['price:asc']]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("science fiction").sort(new String[] {"price:asc"}).build(); client.index("books").search(searchRequest); ``` ```ruby client.index('books').search('science fiction', { sort: ['price:asc'] }) ``` ```go resp, err := client.Index("books").Search("science fiction", &meilisearch.SearchRequest{ Sort: []string{ "price:asc", }, }) ``` ```csharp var sq = new SearchQuery { Sort = new[] { "price:asc" }, }; await client.Index("books").SearchAsync("science fiction", sq); ``` ```rust let results: SearchResults = client .index("books") .search() .with_query("science fiction") .with_sort(&["price:asc"]) .execute() .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/books/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "butler", "sort": ["author:desc"] }' ``` ```js client.index('books').search('butler', { sort: ['author:desc'], }) ``` ```py client.index('books').search('butler', { 'sort': ['author:desc'] }) ``` ```php $client->index('books')->search('butler', ['sort' => ['author:desc']]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("butler").sort(new String[] {"author:desc"}).build(); client.index("books").search(searchRequest); ``` ```ruby client.index('books').search('butler', { sort: ['author:desc'] }) ``` ```go resp, err := client.Index("books").Search("butler", &meilisearch.SearchRequest{ Sort: []string{ "author:desc", }, }) ``` ```csharp var sq = new SearchQuery { Sort = new[] { "author:desc" }, }; await client.Index("books").SearchAsync("butler", sq); ``` ```rust let results: SearchResults = client .index("books") .search() .with_query("butler") .with_sort(&["author:desc"]) .execute() .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/books/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "science fiction", "sort": ["rating.users:asc"] }' ``` ```js client.index('books').search('science fiction', { 'sort': ['rating.users:asc'], }) ``` ```py client.index('books').search('science fiction', { 'sort': ['rating.users:asc'] }) ``` ```php $client->index('books')->search('science fiction', ['sort' => ['rating.users:asc']]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("science fiction").sort(new String[] {"rating.users:asc"}).build(); client.index("books").search(searchRequest); ``` ```ruby client.index('books').search('science fiction', { sort: ['rating.users:asc'] }) ``` ```go resp, err := client.Index("books").Search("science fiction", &meilisearch.SearchRequest{ Sort: []string{ "rating.users:asc", }, }) ``` ```csharp SearchQuery sort = new SearchQuery() { Sort = new string[] { "rating.users:asc" }}; await client.Index("books").SearchAsync("science fiction", sort); ``` ```rust let results: SearchResults = client .index("books") .search() .with_query("science fiction") .with_sort(&["rating.users:asc"]) .execute() .await .unwrap(); ``` ```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 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/?utm_campaign=oss&utm_source=docs&utm_medium=sorting) • [GitHub repository](https://github.com/meilisearch/ecommerce-demo/) - **CRM SaaS demo**: [preview](https://saas.meilisearch.com/?utm_campaign=oss&utm_source=docs&utm_medium=sorting) • [GitHub repository](https://github.com/meilisearch/saas-demo/) --- title: Geosearch — Meilisearch documentation description: Filter and sort search results based on their geographic location. sidebarDepth: 3 --- ## Geosearch 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. _geo field in v0.27, v0.28, and v0.29}> Due to Meilisearch allowing malformed `_geo` fields in the above-mentioned versions, 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 \ -X PUT 'MEILISEARCH_URL/indexes/restaurants/settings/filterable-attributes' \ -H 'Content-type:application/json' \ --data-binary '["_geo"]' ``` ```js client.index('restaurants') .updateFilterableAttributes([ '_geo' ]) ``` ```py client.index('restaurants').update_filterable_attributes([ '_geo' ]) ``` ```php $client->index('restaurants')->updateFilterableAttributes([ '_geo' ]); ``` ```java Settings settings = new Settings(); settings.setFilterableAttributes(new String[] {"_geo"}); client.index("restaurants").updateSettings(settings); ``` ```ruby client.index('restaurants').update_filterable_attributes(['_geo']) ``` ```go filterableAttributes := []string{ "_geo", } client.Index("restaurants").UpdateFilterableAttributes(&filterableAttributes) ``` ```csharp List attributes = new() { "_geo" }; TaskInfo result = await client.Index("movies").UpdateFilterableAttributesAsync(attributes); ``` ```rust let task: TaskInfo = client .index("restaurants") .set_filterable_attributes(&["_geo"]) .await .unwrap(); ``` ```swift client.index("restaurants").updateFilterableAttributes(["_geo"]) { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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` establishes a circular area based on a central point and a radius. This filter rule requires three parameters: `lat`, `lng` and `distance_in_meters`. ``` _geoRadius(lat, lng, distance_in_meters) ``` `lat` and `lng` must be floating point numbers indicating a geographic position. `distance_in_meters` must be an integer indicating the radius covered by the `_geoRadius` filter. #### `_geoBoundingBox` `_geoBoundingBox` establishes a rectangular area based on the coordinates for its top right and bottom left corners. This filter rule requires two arrays: ``` _geoBoundingBox([{lat}, {lng}], [{lat}, {lng}]) ``` `lat` and `lng` must be floating point numbers indicating a geographic position. The first array indicates the geographic coordinates of the top right corner of the rectangular area. The second array indicates the coordinates of the bottom left corner of the rectangular area. #### Examples Using our example dataset, we can search for places to eat near the center of Milan with `_geoRadius`: ```bash curl \ -X POST 'MEILISEARCH_URL/indexes/restaurants/search' \ -H 'Content-type:application/json' \ --data-binary '{ "filter": "_geoRadius(45.472735, 9.184019, 2000)" }' ``` ```js client.index('restaurants').search('', { filter: ['_geoRadius(45.472735, 9.184019, 2000)'], }) ``` ```py client.index('restaurants').search('', { 'filter': '_geoRadius(45.472735, 9.184019, 2000)' }) ``` ```php $client->index('restaurants')->search('', [ 'filter' => '_geoRadius(45.472735, 9.184019, 2000)' ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("").filter(new String[] {"_geoRadius(45.472735, 9.184019, 2000)"}).build(); client.index("restaurants").search(searchRequest); ``` ```ruby client.index('restaurants').search('', { filter: '_geoRadius(45.472735, 9.184019, 2000)' }) ``` ```go resp, err := client.Index("restaurants").Search("", &meilisearch.SearchRequest{ Filter: "_geoRadius(45.472735, 9.184019, 2000)", }) ``` ```csharp SearchQuery filters = new SearchQuery() { Filter = "_geoRadius(45.472735, 9.184019, 2000)" }; var restaurants = await client.Index("restaurants").SearchAsync("", filters); ``` ```rust let results: SearchResults = client .index("restaurants") .search() .with_filter("_geoRadius(45.472735, 9.184019, 2000)") .execute() .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/restaurants/search' \ -H 'Content-type:application/json' \ --data-binary '{ "filter": "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])" }' ``` ```js client.index('restaurants').search('', { filter: ['_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])'], }) ``` ```py client.index('restaurants').search('Batman', { 'filter': '_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])' }) ``` ```php $client->index('restaurants')->search('', [ 'filter' => '_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])' ]); ``` ```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 client.index('restaurants').search('', { filter: ['_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])'] }) ``` ```go client.Index("restaurants").Search("", &meilisearch.SearchRequest{ Filter: "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])", }) ``` ```csharp SearchQuery filters = new SearchQuery() { Filter = "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])" }; var restaurants = await client.Index("restaurants").SearchAsync("restaurants", filters); ``` ```rust let results: SearchResults = client .index("restaurants") .search() .with_filter("_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])") .execute() .await .unwrap(); ``` ```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 await client.index('restaurants').search( '', SearchQuery( filter: '_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])', ), ); ``` In both cases, the results should look like this: ```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 \ -X POST 'MEILISEARCH_URL/indexes/restaurants/search' \ -H 'Content-type:application/json' \ --data-binary '{ "filter": "_geoRadius(45.472735, 9.184019, 2000) AND type = pizza" }' ``` ```js client.index('restaurants').search('', { filter: ['_geoRadius(45.472735, 9.184019, 2000) AND type = pizza'], }) ``` ```py client.index('restaurants').search('', { 'filter': '_geoRadius(45.472735, 9.184019, 2000) AND type = pizza' }) ``` ```php $client->index('restaurants')->search('', [ 'filter' => '_geoRadius(45.472735, 9.184019, 2000) AND type = pizza' ]); ``` ```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 client.index('restaurants').search('', { filter: '_geoRadius(45.472735, 9.184019, 2000) AND type = pizza' }) ``` ```go resp, err := client.Index("restaurants").Search("", &meilisearch.SearchRequest{ Filter: "_geoRadius(45.472735, 9.184019, 2000) AND type = pizza", }) ``` ```csharp 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 let results: SearchResults = client .index("restaurants") .search() .with_filter("_geoRadius(45.472735, 9.184019, 2000) AND type = pizza") .execute() .await .unwrap(); ``` ```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 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 } } ] ``` The above command will only work if you have previously added `type` to `filterableAttributes`. `_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` You can use `_geo` data to sort results based on their distance from a specific location. #### Configuration Before using geosearch for sorting, you must add the `_geo` attribute to the `sortableAttributes` list: ```bash curl \ -X PUT 'MEILISEARCH_URL/indexes/restaurants/settings/sortable-attributes' \ -H 'Content-type:application/json' \ --data-binary '["_geo"]' ``` ```js client.index('restaurants').updateSortableAttributes([ '_geo' ]) ``` ```py client.index('restaurants').update_sortable_attributes([ '_geo' ]) ``` ```php $client->index('restaurants')->updateSortableAttributes([ '_geo' ]); ``` ```java client.index("restaurants").updateSortableAttributesSettings(new String[] {"_geo"}); ``` ```ruby client.index('restaurants').update_sortable_attributes(['_geo']) ``` ```go sortableAttributes := []string{ "_geo", } client.Index("restaurants").UpdateSortableAttributes(&sortableAttributes) ``` ```csharp List attributes = new() { "_geo" }; TaskInfo result = await client.Index("restaurants").UpdateSortableAttributesAsync(attributes); ``` ```rust let task: TaskInfo = client .index("restaurants") .set_sortable_attributes(&["_geo"]) .await .unwrap(); ``` ```swift client.index("restaurants").updateSortableAttributes(["_geo"]) { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```dart await client.index('restaurants').updateSortableAttributes(['_geo']); ``` Note that Meilisearch will rebuild your index whenever you update `sortableAttributes`. Depending on the size of your dataset, this might take a considerable amount of time. [You can read more about configuring `sortableAttributes` in our dedicated sorting guide.](/learn/filtering_and_sorting/sort_search_results) #### Usage First, ensure your documents contain valid geolocation data and that you have added the `_geo` attribute to the `sortableAttributes` list. Then, you can use the [`sort` search parameter](/reference/api/search#sort) along with `_geoPoint`, a special sorting function, to order results based on their distance from a geographic location. ``` _geoPoint(0.0, 0.0):asc ``` `_geoPoint` 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`). Ascending sort will prioritize results closer to the specified location, while descending sort will put the most distant results first. If either `lat` or `lng` is invalid or missing, Meilisearch will return an [`invalid_search_sort`](/reference/errors/error_codes#invalid_search_sort) error. An error will also be thrown if you fail to indicate a sorting order. [You can read more about sorting in our dedicated guide.](/learn/filtering_and_sorting/sort_search_results) `_geo`, `_geoDistance`, and `_geoRadius` are not valid `sort` values. Trying to use any of them with the `sort` search parameter will result in an [`invalid_search_sort`](/reference/errors/error_codes#invalid_search_sort) error. #### 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 \ -X POST 'MEILISEARCH_URL/indexes/restaurants/search' \ -H 'Content-type:application/json' \ --data-binary '{ "sort": ["_geoPoint(48.8561446,2.2978204):asc"] }' ``` ```js client.index('restaurants').search('', { sort: ['_geoPoint(48.8561446, 2.2978204):asc'], }) ``` ```py client.index('restaurants').search('', { 'sort': ['_geoPoint(48.8561446,2.2978204):asc'] }) ``` ```php $client->index('restaurants')->search('', [ 'sort' => ['_geoPoint(48.8561446,2.2978204):asc'] ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("").sort(new String[] {"_geoPoint(48.8561446,2.2978204):asc"}).build(); client.index("restaurants").search(searchRequest); ``` ```ruby client.index('restaurants').search('', { sort: ['_geoPoint(48.8561446, 2.2978204):asc'] }) ``` ```go resp, err := client.Index("restaurants").Search("", &meilisearch.SearchRequest{ Sort: []string{ "_geoPoint(48.8561446,2.2978204):asc", }, }) ``` ```csharp SearchQuery filters = new SearchQuery() { Sort = new string[] { "_geoPoint(48.8561446,2.2978204):asc" } }; var restaurants = await client.Index("restaurants").SearchAsync("", filters); ``` ```rust let results: SearchResults = client .index("restaurants") .search() .with_sort(&["_geoPoint(48.8561446, 2.2978204):asc"]) .execute() .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/restaurants/search' \ -H 'Content-type:application/json' \ --data-binary '{ "sort": [ "_geoPoint(48.8561446,2.2978204):asc", "rating:desc" ] }' ``` ```js client.index('restaurants').search('', { sort: ['_geoPoint(48.8561446, 2.2978204):asc', 'rating:desc'], }) ``` ```py client.index('restaurants').search('', { 'sort': ['_geoPoint(48.8561446,2.2978204):asc', 'rating:desc'] }) ``` ```php $client->index('restaurants')->search('', [ 'sort' => ['_geoPoint(48.8561446,2.2978204):asc', 'rating:desc'] ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q()("").sort(new String[] { "_geoPoint(48.8561446,2.2978204):asc", "rating:desc", }).build(); client.index("restaurants").search(searchRequest); ``` ```ruby client.index('restaurants').search('', { sort: ['_geoPoint(48.8561446, 2.2978204):asc', 'rating:desc'] }) ``` ```go resp, err := client.Index("restaurants").Search("", &meilisearch.SearchRequest{ Sort: []string{ "_geoPoint(48.8561446,2.2978204):asc", "rating:desc", }, }) ``` ```csharp 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 let results: SearchResults = client .index("restaurants") .search() .with_sort(&["_geoPoint(48.8561446, 2.2978204):asc", "rating:desc"]) .execute() .await .unwrap(); ``` ```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 await client.index('restaurants').search( '', SearchQuery( sort: ['_geoPoint(48.8561446, 2.2978204):asc', 'rating:desc'])); ``` The above command will only work if you have previously added `rating` to `sortableAttributes`. ```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 } } ] ``` #### Ranking rules By default, Meilisearch emphasizes relevant sorting over exhaustive sorting. This means our engine first finds the most relevant results and only then orders matches based on values given to the `sort` search parameter. As a result, sorting with `_geoPoint` will rarely be the most important factor in deciding which results users see first. More often, it will be a tie-breaker between results that are considered equally relevant to the given search query. Since `_geoPoint` is part of the `sort` search parameter, its weight when ranking results is controlled by the position of the `"sort"` rule in the `rankingRules` array. [You can read more about the `"sort"` ranking rule and how to customize it in our dedicated sorting guide.](/learn/filtering_and_sorting/sort_search_results#sorting-and-custom-ranking-rules) ### Finding the distance between a document and a `_geoPoint` When using `_geoPoint`, all returned documents will contain one extra field: `_geoDistance`. As its name indicates, `_geoDistance` contains the distance in meters between the specified `_geoPoint` and a document's `_geo` 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 }, "_geoDistance": 1532 } ] ``` Using `_geoRadius` filter will not cause results to include `_geoDistance`. `_geoDistance` will only be computed in a returned document if the query uses `_geoPoint` and the `sort` search parameter. Additionally, returned documents will only include `_geoDistance` if `_geo` is present in the [`displayedAttributes` list](/learn/relevancy/displayed_searchable_attributes). --- title: Filter expression reference — Meilisearch documentation description: The `filter` search parameter expects a filter expression. Filter expressions are made of attributes, values, and several operators. --- ## Filter expression reference 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 only apply only to numerical values. The expression below returns all documents with a user rating above 85: ``` rating.users > 85 ``` #### `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 ``` #### `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 }' ``` This will also enable the [`STARTS WITH`](#starts-with) operator. #### `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 ``` 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 }' ``` This will also enable the [`CONTAINS`](#contains) operator. #### `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'"] ``` --- title: Securing your project — Meilisearch documentation description: This tutorial will show you how to secure your Meilisearch project. --- ## Securing your 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: ![An interface element named "API keys" showing three obscured keys: "Master key", "Default Search API Key", and "Default Admin API Key"](https://raw.githubusercontent.com/meilisearch/documentation/cloud-master-api-keys/assets/images/security/01-master-api-keys.png) #### 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: ![An interface element named "API keys" showing three obscured keys: "Master key", "Default Search API Key", and "Default Admin API Key"](https://raw.githubusercontent.com/meilisearch/documentation/cloud-master-api-keys/assets/images/security/01-master-api-keys.png) #### 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 -X GET 'MEILISEARCH_URL/keys' \ -H 'Authorization: Bearer MASTER_KEY' ``` 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 \ -X POST 'MEILISEARCH_URL/indexes' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer DEFAULT_ADMIN_API_KEY' \ --data-binary '{ "uid": "medical_records", "primaryKey": "id" }' ``` Then use the `Default Search API Key` to perform search operations in the index you just created: ```bash 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" }' ``` ### 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. --- title: Multitenancy and tenant tokens — Meilisearch documentation description: This guide shows you the main steps when creating tenant tokens using Meilisearch's official SDKs. --- ## Using tenant tokens with an official SDK 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: ```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 }) ``` ```py 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 $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 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 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 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 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 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: ```js const frontEndClient = new MeiliSearch({ host: 'http://localhost:7700', apiKey: token }) frontEndClient.index('patient_medical_records').search('blood test') ``` ```py front_end_client = Client('http://localhost:7700', token) front_end_client.index('patient_medical_records').search('blood test') ``` ```php $frontEndClient = new Client('http://localhost:7700', $token); $frontEndClient->index('patient_medical_records')->search('blood test'); ``` ```java Client frontEndClient = new Client(new Config("http://localhost:7700", token)); frontEndClient.index("patient_medical_records").search("blood test"); ``` ```ruby front_end_client = MeiliSearch::Client.new('http://localhost:7700', token) front_end_client.index('patient_medical_records').search('blood test') ``` ```go client := meilisearch.New("http://localhost:7700", meilisearch.WithAPIKey("masterKey")) client.Index("patient_medical_records").Search("blood test", &meilisearch.SearchRequest{}); ``` ```csharp frontEndClient = new MeilisearchClient("http://localhost:7700", token); var searchResult = await frontEndClient.Index("patient_medical_records").SearchAsync("blood test"); ``` ```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. --- title: Generate tenant tokens without a Meilisearch SDK — Meilisearch documentation description: This guide shows you the main steps when creating tenant tokens without using Meilisearch's official SDKs. --- ## Generate tenant tokens without a Meilisearch SDK 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 \ -X POST 'MEILISEARCH_URL/indexes/patient_medical_records/search' \ -H 'Authorization: Bearer TENANT_TOKEN' ``` --- title: Generate a tenant token without a library — Meilisearch documentation description: This guide shows you the main steps when creating tenant tokens without using any libraries. --- ## Generate a tenant token without a library 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 \ -X POST 'MEILISEARCH_URL/indexes/patient_medical_records/search' \ -H 'Authorization: Bearer TENANT_TOKEN' ``` --- title: Resetting the master key — Meilisearch documentation description: This guide shows you how to reset the master key in Meilisearch Cloud and self-hosted instances. --- ## Resetting the master key 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. --- title: Tenant token payload reference — Meilisearch documentation description: "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." --- ## Tenant token payload reference 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. --- title: Differences between the master key and API keys — Meilisearch documentation description: "This article explains the main usage differences between the two types of security keys in Meilisearch: master key and API keys." --- ## Differences between the 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. --- title: Protected and unprotected Meilisearch projects — Meilisearch documentation description: This article explains the differences between protected and unprotected Meilisearch projects and instances. --- ## Protected and unprotected Meilisearch projects 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. --- title: Multitenancy and tenant tokens — Meilisearch documentation description: In this article you'll read what multitenancy is and how tenant tokens help managing complex applications and sensitive data. --- ## Multitenancy and tenant tokens 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_campaign=oss&utm_source=docs&utm_medium=tenant-tokens). 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. --- title: Using multi-search to perform a federated search — Meilisearch API reference description: 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. --- ## Using multi-search to perform a federated search 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. --- title: Differences between multi-search and federated search — Meilisearch API reference description: This article defines multi-search and federated search and then describes the different uses of each. --- ## Differences between multi-search and federated search 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. --- title: Migrating to Meilisearch Cloud — Meilisearch Documentation description: Meilisearch Cloud is the recommended way of using Meilisearch. This guide walks you through migrating Meilisearch from a self-hosted installation to Meilisearch Cloud. --- ## Migrating 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/advanced/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: ![The Meilisearch Cloud menu, featuring the "New Project" button](/assets/images/cloud-migration/1-new-project.png) 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: ![A modal window with three mandatory fields: "Project name", "Select a region", and "Select a plan". Further down, an optional field: "Import .dump"](/assets/images/cloud-migration/2-import-dump.png) 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). --- title: Update to the latest Meilisearch version — Meilisearch documentation description: Learn how to migrate to the latest Meilisearch release. sidebarDepth: 3 --- ## Update to the latest Meilisearch version 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/advanced/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. ![Button to update Meilisearch version to 1.0.2](/assets/images/updating/update-button.png) 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". ![Project update in progress](/assets/images/updating/update-in-progress.png) 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 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. #### 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 \ -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 \ -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 # 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 \ -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 ``` _geo field in v0.27, v0.28, and v0.29}> 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 \ -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` --- title: Migrating from Algolia to Meilisearch — Meilisearch documentation description: This guide will take you step-by-step through the creation of a Node.js script to upload data indexed by Algolia to Meilisearch. sidebarDepth: 3 --- ## Migrating from 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. --- title: Accessing previous docs versions — Meilisearch documentation description: Meilisearch documentation only covers the engine's latest stable release. Learn how to access the docs for previous Meilisearch versions. sidebarDepth: 4 --- ## Accessing previous docs 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. --- title: Exporting and importing dumps — Meilisearch documentation description: Dumps are data backups containing all data related to a Meilisearch instance. They are often useful when migrating to a new Meilisearch release. --- ## Exporting and importing dumps A [dump](/learn/advanced/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: ![The General settings interface displaying various data fields relating to a Meilisearch Cloud project. One of them reads "Meilisearch version". Its value is `v1.6.2`. Next to the value is a button "Update to v1.7.0"](/assets/images/cloud-dumps/01-export-dump.png) 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 \ -X POST 'MEILISEARCH_URL/dumps' ``` ```js client.createDump() ``` ```py client.create_dump() ``` ```php $client->createDump(); ``` ```java client.createDump(); ``` ```ruby client.create_dump ``` ```go resp, err := client.CreateDump() ``` ```csharp await client.CreateDumpAsync(); ``` ```rust client .create_dump() .await .unwrap(); ``` ```swift client.createDump { result in switch result { case .success(let dumpStatus): print(dumpStatus) case .failure(let error): print(error) } } ``` ```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 \ -X GET 'MEILISEARCH_URL/tasks/1' ``` ```js client.getTask(1) ``` ```py client.get_task(1) ``` ```php $client->getTask(1); ``` ```java client.getTask(1); ``` ```ruby client.task(1) ``` ```go client.GetTask(1); ``` ```csharp TaskInfo task = await client.GetTaskAsync(1); ``` ```rust let task: Task = client .get_task(1) .await .unwrap(); ``` ```swift client.getTask(taskUid: 1) { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 start 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: ![The project creation interface, with a few inputs fields: project name, region selection, and plan selection. Right below all of these, is a file upload button named "Import .dump"](/assets/images/cloud-dumps/02-import-dump.png) #### 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. --- title: Exporting and using Snapshots — Meilisearch documentation description: Snapshots are exact copies of Meilisearch databases. They are often useful for periodical backups. --- ## Exporting and using snapshots A [snapshot](/learn/advanced/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. --- title: Snapshots and dumps — Meilisearch documentation description: "Meilisearch offers two types of backups: snapshots and dumps. Snapshots are mainly intended as a safeguard, while dumps are useful when migrating Meilisearch." --- ## Snapshots and dumps 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/advanced/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.** --- title: Indexing best practices — Meilisearch documentation description: Tips to speed up your documents indexing process. --- ## Indexing best practices 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. --- title: Impact of RAM and multi-threading on indexing performance — Meilisearch documentation description: Adding new documents to a Meilisearch index is a multi-threaded and memory-intensive operation. Consult this article for more information on indexing performance. --- ## Impact of RAM and multi-threading 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) --- title: Tokenization — Meilisearch documentation description: 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 **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). --- title: Concatenated and split queries — Meilisearch documentation description: When a query contains several terms, Meilisearch looks for both individual terms and their combinations. --- ## Concatenated and split queries ### 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. --- title: Data types — Meilisearch documentation description: "Learn about how Meilisearch handles different data types: strings, numerical values, booleans, arrays, and objects." --- ## Data types 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. --- title: Prefix search — Meilisearch documentation description: 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. --- ## Prefix search 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). --- title: Storage — Meilisearch documentation description: Learn about how Meilisearch stores and handles data in its LMDB storage engine. --- ## Storage 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. --- title: Relevancy — Meilisearch documentation description: 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 **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. --- title: Built-in ranking rules — Meilisearch documentation description: Built-in ranking rules are the core of Meilisearch's relevancy calculations. --- ## Built-in ranking rules 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 custom 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 ![Demonstrating the typo ranking rule by searching for 'vogli'](/assets/images/ranking-rules/vogli3.png) ###### Typo - `vogli`: 0 typo - `volli`: 1 typo The `typo` rule sorts the results by increasing number of typos on matched query words. ![Demonstrating the proximity ranking rule by searching for 'new road'](/assets/images/ranking-rules/new_road.png) ###### 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. ![Demonstrating the attribute ranking rule by searching for 'belgium'](/assets/images/ranking-rules/belgium.png) ###### 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). ![Demonstrating the exactness ranking rule by searching for 'Knight'](/assets/images/ranking-rules/knight.png?raw=true) ###### 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`. --- title: Custom ranking rules — Meilisearch documentation description: Custom ranking rules promote certain documents over other search results that are otherwise equally relevant. --- ## Custom ranking rules 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). --- title: Ranking score — Meilisearch documentation description: This article explains how the order of attributes in the `searchableAttributes` array impacts search result relevancy. --- ## Ranking score 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 | --- title: Attribute ranking order — Meilisearch documentation description: This article explains how the order of attributes in the `searchableAttributes` array impacts search result relevancy. --- ## Attribute ranking order 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 claculating 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.** --- title: Typo tolerance settings — Meilisearch documentation description: This article describes each of the typo tolerance settings. --- ## 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 \ -X PATCH 'MEILISEARCH_URL/indexes/movies/settings/typo-tolerance' \ -H 'Content-Type: application/json' \ --data-binary '{ "enabled": false }' ``` ```js client.index('movies').updateTypoTolerance({ enabled: false }) ``` ```py client.index('movies').update_typo_tolerance({ 'enabled': False }) ``` ```php $client->index('movies')->updateTypoTolerance([ 'enabled' => false ]); ``` ```java TypoTolerance typoTolerance = new TypoTolerance(); typoTolerance.setEnabled(false); client.index("movies").updateTypoToleranceSettings(typoTolerance); ``` ```ruby index('books').update_typo_tolerance({ enabled: false }) ``` ```go client.Index("movies").UpdateTypoTolerance(&meilisearch.TypoTolerance{ Enabled: false, }) ``` ```csharp var typoTolerance = new TypoTolerance { Enabled = false }; await client.Index("movies").UpdateTypoToleranceAsync(typoTolerance); ``` ```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 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 \ -X PATCH 'MEILISEARCH_URL/indexes/movies/settings/typo-tolerance' \ -H 'Content-Type: application/json' \ --data-binary '{ "minWordSizeForTypos": { "oneTypo": 4, "twoTypos": 10 } }' ``` ```js client.index('movies').updateTypoTolerance({ minWordSizeForTypos: { oneTypo: 4, twoTypos: 10 } }) ``` ```py client.index('movies').update_typo_tolerance({ 'minWordSizeForTypos': { 'oneTypo': 4, 'twoTypos': 10 } }) ``` ```php $client->index('movies')->updateTypoTolerance([ 'minWordSizeForTypos' => [ 'oneTypo' => 4, 'twoTypos' => 10 ] ]); ``` ```java TypoTolerance typoTolerance = new TypoTolerance(); HashMap minWordSizeTypos = new HashMap() { { put("oneTypo", 4); put("twoTypos", 10); } }; typoTolerance.setMinWordSizeForTypos(minWordSizeTypos); client.index("movies").updateTypoToleranceSettings(typoTolerance); ``` ```ruby index('books').update_typo_tolerance({ min_word_size_for_typos: { one_typo: 4, two_typos: 10 } }) ``` ```go client.Index("movies").UpdateTypoTolerance(&meilisearch.TypoTolerance{ MinWordSizeForTypos: meilisearch.MinWordSizeForTypos{ OneTypo: 4, TwoTypos: 10, }, }) ``` ```csharp var typoTolerance = new TypoTolerance { MinWordSizeTypos = new TypoTolerance.TypoSize { OneTypo = 4, TwoTypos = 10 } }; await client.Index("movies").UpdateTypoToleranceAsync(typoTolerance); ``` ```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 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 \ -X PATCH 'MEILISEARCH_URL/indexes/movies/settings/typo-tolerance' \ -H 'Content-Type: application/json' \ --data-binary '{ "disableOnWords": [ "shrek" ] }' ``` ```js client.index('movies').updateTypoTolerance({ disableOnWords: ['shrek'] }) ``` ```py client.index('movies').update_typo_tolerance({ 'disableOnWords': ['shrek'] }) ``` ```php $client->index('movies')->updateTypoTolerance([ 'disableOnWords' => ['shrek'] ]); ``` ```java TypoTolerance typoTolerance = new TypoTolerance(); typoTolerance.setDisableOnWords(new String[] {"shrek"}); client.index("movies").updateTypoToleranceSettings(typoTolerance); ``` ```ruby index('books').update_typo_tolerance({ disable_on_words: ['shrek'] }) ``` ```go client.Index("movies").UpdateTypoTolerance(&meilisearch.TypoTolerance{ DisableOnWords: []string{"shrek"}, }) ``` ```csharp var typoTolerance = new TypoTolerance { DisableOnWords = new string[] { "shrek" } }; await client.Index("movies").UpdateTypoToleranceAsync(typoTolerance); ``` ```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 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 \ -X PATCH 'MEILISEARCH_URL/indexes/movies/settings/typo-tolerance' \ -H 'Content-Type: application/json' \ --data-binary '{ "disableOnAttributes": ["title"] }' ``` ```js client.index('movies').updateTypoTolerance({ disableOnAttributes: ['title'] }) ``` ```py client.index('movies').update_typo_tolerance({ 'disableOnAttributes': ['title'] }) ``` ```php $client->index('movies')->updateTypoTolerance([ 'disableOnAttributes' => ['title'] ]); ``` ```java TypoTolerance typoTolerance = new TypoTolerance(); typoTolerance.setDisableOnAttributes(new String[] {"title"}); client.index("movies").updateTypoToleranceSettings(typoTolerance); ``` ```ruby index('books').update_typo_tolerance({ disable_on_attributes: ['title'] }) ``` ```go client.Index("movies").UpdateTypoTolerance(&meilisearch.TypoTolerance{ DisableOnAttributes: []string{"title"}, }) ``` ```csharp var typoTolerance = new TypoTolerance { DisableOnAttributes = new string[] { "title" } }; await client.Index("movies").UpdateTypoToleranceAsync(typoTolerance); ``` ```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 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. --- title: Typo tolerance calculations — Meilisearch documentation description: Typo tolerance helps users find relevant results even when their search queries contain spelling mistakes or typos. --- ## Typo tolerance calculations 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` --- title: Distinct attribute — Meilisearch documentation description: 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. --- ## Distinct attribute 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 \ -X PUT 'MEILISEARCH_URL/indexes/jackets/settings/distinct-attribute' \ -H 'Content-Type: application/json' \ --data-binary '"product_id"' ``` ```js client.index('jackets').updateDistinctAttribute('product_id') ``` ```py client.index('jackets').update_distinct_attribute('product_id') ``` ```php $client->index('jackets')->updateDistinctAttribute('product_id'); ``` ```java client.index("jackets").updateDistinctAttributeSettings("product_id"); ``` ```ruby client.index('jackets').update_distinct_attribute('product_id') ``` ```go client.Index("jackets").UpdateDistinctAttribute("product_id") ``` ```csharp await client.Index("jackets").UpdateDistinctAttributeAsync("product_id"); ``` ```rust let task: TaskInfo = client .index("jackets") .set_distinct_attribute("product_id") .await .unwrap(); ``` ```swift client.index("jackets").updateDistinctAttribute("product_id") { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X PUT 'MEILISEARCH_URL/indexes/products/settings/filterable-attributes' \ -H 'Content-Type: application/json' \ --data-binary '[ "product_id", "sku", "url" ]' ``` ```js client.index('products').updateFilterableAttributes(['product_id', 'sku', 'url']) ``` ```py client.index('products').update_filterable_attributes(['product_id', 'sku', 'url']) ``` ```php $client->index('products')->updateFilterableAttributes(['product_id', 'sku', 'url']); ``` ```java Settings settings = new Settings(); settings.setFilterableAttributes(new String[] {"product_id", "SKU", "url"}); client.index("products").updateSettings(settings); ``` ```ruby client.index('products').update_filterable_attributes([ 'product_id', 'sku', 'url' ]) ``` ```go filterableAttributes := []string{ "product_id", "sku", "url", } client.Index("products").UpdateFilterableAttributes(&filterableAttributes) ``` ```csharp List attributes = new() { "product_id", "sku", "url" }; TaskInfo result = await client.Index("products").UpdateFilterableAttributesAsync(attributes); ``` ```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 \ -X POST 'MEILISEARCH_URL/indexes/products/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "white shirt", "distinct": "sku" }' ``` ```js client.index('products').search('white shirt', { distinct: 'sku' }) ``` ```py client.index('products').search('white shirt', { distinct: 'sku' }) ``` ```php $client->index('products')->search('white shirt', [ 'distinct' => 'sku' ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("white shirt").distinct("sku").build(); client.index("products").search(searchRequest); ``` ```ruby client.index('products').search('white shirt', { distinct: 'sku' }) ``` ```go client.Index("products").Search("white shirt", &meilisearch.SearchRequest{ Distinct: "sku", }) ``` ```csharp var params = new SearchQuery() { Distinct = "sku" }; await client.Index("products").SearchAsync("white shirt", params); ``` ```rust let res = client .index("products") .search() .with_query("white shirt") .with_distinct("sku") .execute() .await .unwrap(); ``` --- title: Displayed and searchable attributes — Meilisearch documentation description: 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. --- ## Displayed and searchable attributes 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 \ -X PUT 'MEILISEARCH_URL/indexes/movies/settings/displayed-attributes' \ -H 'Content-Type: application/json' \ --data-binary '[ "title", "overview", "genres", "release_date" ]' ``` ```js client.index('movies').updateDisplayedAttributes([ 'title', 'overview', 'genres', 'release_date', ] ) ``` ```py client.index('movies').update_displayed_attributes([ 'title', 'overview', 'genres', 'release_date' ]) ``` ```php $client->index('movies')->updateDisplayedAttributes([ 'title', 'overview', 'genres', 'release_date' ]); ``` ```java String[] attributes = {"title", "overview", "genres", "release_date"} client.index("movies").updateDisplayedAttributesSettings(attributes); ``` ```ruby client.index('movies').update_settings({ displayed_attributes: [ 'title', 'overview', 'genres', 'release_date' ] }) ``` ```go displayedAttributes := []string{ "title", "overview", "genres", "release_date", } client.Index("movies").UpdateDisplayedAttributes(&displayedAttributes) ``` ```csharp await client.Index("movies").UpdateDisplayedAttributesAsync(new[] { "title", "overview", "genres", "release_date" }); ``` ```rust let displayed_attributes = [ "title", "overvieww", "genres", "release_date" ]; let task: TaskInfo = client .index("movies") .set_displayed_attributes(&displayed_attributes) .await .unwrap(); ``` ```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 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 \ -X PUT 'MEILISEARCH_URL/indexes/movies/settings/searchable-attributes' \ -H 'Content-Type: application/json' \ --data-binary '[ "title", "overview", "genres" ]' ``` ```js client.index('movies').updateSearchableAttributes([ 'title', 'overview', 'genres', ] ) ``` ```py client.index('movies').update_searchable_attributes([ 'title', 'overview', 'genres' ]) ``` ```php $client->index('movies')->updateSearchableAttributes([ 'title', 'overview', 'genres' ]); ``` ```java String[] attributes = {"title", "overview", "genres"} client.index("movies").updateSearchableAttributesSettings(attributes); ``` ```ruby client.index('movies').update_searchable_attributes([ 'title', 'overview', 'genres' ]) ``` ```go searchableAttributes := []string{ "title", "overview", "genres", } client.Index("movies").UpdateSearchableAttributes(&searchableAttributes) ``` ```csharp await client.Index("movies").UpdateSearchableAttributesAsync(new[] { "title", "overview", "genres" }); ``` ```rust let searchable_attributes = [ "title", "overvieww", "genres" ]; let task: TaskInfo = client .index("movies") .set_searchable_attributes(&searchable_attributes) .await .unwrap(); ``` ```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 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. --- title: Synonyms — Meilisearch documentation description: Use Meilisearch synonyms to indicate sets of query terms which should be considered equivalent during search. --- ## Synonyms 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 \ -X PUT 'MEILISEARCH_URL/indexes/movies/settings/synonyms' \ -H 'Content-Type: application/json' \ --data-binary '{ "great": ["fantastic"], "fantastic": ["great"] }' ``` ```js client.index('movies').updateSynonyms({ 'great': ['fantastic'], 'fantastic': ['great'] }) ``` ```py client.index('movies').update_synonyms({ 'great': ['fantastic'], 'fantastic': ['great'] }) ``` ```php $client->index('movies')->updateSynonyms([ 'great' => ['fantastic'], 'fantastic' => ['great'], ]); ``` ```java HashMap synonyms = new HashMap(); synonyms.put("great", new String[] {"fantastic"}); synonyms.put("fantastic", new String[] {"great"}); client.index("movies").updateSynonymsSettings(synonyms); ``` ```ruby client.index('movies').update_synonyms({ great: ['fantastic'], fantastic: ['great'] }) ``` ```go synonyms := map[string][]string{ "great": []string{"fantastic"}, "fantastic": []string{"great"}, } client.Index("movies").UpdateSynonyms(&synonyms) ``` ```csharp var synonyms = new Dictionary> { { "great", new string[] { "fantastic" } }, { "fantastic", new string[] { "great" } } }; await client.Index("movies").UpdateSynonymsAsync(synonyms); ``` ```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 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 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. --- title: Known limitations — Meilisearch documentation description: Meilisearch has a number of known limitations. These are hard limits you cannot change and should take into account when designing your application. --- ## Known limitations Currently, 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 guide 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 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. --- title: Experimental features overview — Meilisearch documentation description: This article covers how to activate activate and configure Meilisearch experimental features. --- ## 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 \ -X PATCH 'MEILISEARCH_URL/experimental-features/' \ -H 'Content-Type: application/json' \ --data-binary '{ "metrics": true }' ``` ```go client.ExperimentalFeatures().SetMetrics(true).Update() ``` ```rust let client = Client::new("http://localhost:7700", Some("apiKey")); let mut features = ExperimentalFeatures::new(&client); features.set_vector_store(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 | At launch with a CLI flag or environment variable | | [Log customization](/reference/api/logs) | Customize log output and set up log streams | At launch with a CLI flag or environment variable, during runtime with the API route | | [Metrics API](/reference/api/metrics) | Exposes Prometheus-compatible analytics data | At launch with a CLI flag or environment variable, during runtime with the API route | | [Reduce indexing memory usage](/learn/self_hosted/configure_meilisearch_at_launch) | Optimizes indexing performance | At launch with a CLI flag or environment variable | | [Replication parameters](/learn/self_hosted/configure_meilisearch_at_launch) | Alters task processing for clustering compatibility | At launch with a CLI flag or environment variable | | [Search queue size](/learn/self_hosted/configure_meilisearch_at_launch) | Configure maximum number of concurrent search requests | At launch with a CLI flag or environment variable | | [Vector store](/learn/ai_powered_search/getting_started_with_ai_search) | Allows Meilisearch to function as a vector embedding store | During runtime with the API route | | [`CONTAINS` filter operator](/learn/filtering_and_sorting/filter_expression_reference#contains) | Enables usage of `CONTAINS` with the `filter` search parameter | During runtime with the 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 | During runtime with the API route | --- title: FAQ — Meilisearch documentation description: Frequently asked questions --- ## FAQ ### 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). --- title: Official SDKs and libraries — Meilisearch documentation description: Meilisearch SDKs are available in many popular programming languages and frameworks. Consult this page for a full list of officially supported libraries. --- ## Official SDKs and 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-aws](https://github.com/meilisearch/cloud-providers/) - Guide: [How to deploy a Meilisearch instance on Amazon Web Services](/guides/deployment/aws) - [meilisearch-digitalocean](https://github.com/meilisearch/cloud-providers/) - Guide: [How to deploy a Meilisearch instance on DigitalOcean](/guides/deployment/digitalocean) - [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) ### 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. --- title: Comparison to alternatives — Meilisearch documentation description: "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." sidebarDepth: 4 --- ## Comparison to alternatives 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) | SSPL
([Not open-source](https://opensource.org/node/1099)) | | 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 | ❌
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 | ❌ | | 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/cloud/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. --- title: Telemetry — Meilisearch documentation description: 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. sidebarDepth: 2 --- ## 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 ## The following procedure should work for all cloud providers, ## including DigitalOcean, Google Cloud Platform, and Amazon Web Services. ## 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 | 0.23.0 | `infos.env` | Value of `--env`/`MEILI_ENV` | production | `infos.db_path` | `true` if `--db-path`/`MEILI_DB_PATH` is specified, otherwise `false` | true | `infos.import_dump` | `true` if `--import-dump` is specified, otherwise `false` | true | `infos.dump_dir` | `true` if `--dump-dir`/`MEILI_DUMP_DIR` is specified, otherwise `false` | true | `infos.ignore_missing_dump` | `true` if `--ignore-missing-dump` is activated, otherwise `false` | true | `infos.ignore_dump_if_db_exists` | `true` if `--ignore-dump-if-db-exists` is activated, otherwise `false` | true | `infos.import_snapshot` | `true` if `--import-snapshot` is specified, otherwise `false` | true | `infos.schedule_snapshot` | Value of `--schedule_snapshot`/`MEILI_SCHEDULE_SNAPSHOT` if scheduled snapshots are enabled, otherwise `None` | 86400 | `infos.snapshot_dir` | `true` if `--snapshot-dir`/`MEILI_SNAPSHOT_DIR` is specified, otherwise `false` | true | `infos.ignore_missing_snapshot` | `true` if `--ignore-missing-snapshot` is activated, otherwise `false` | true | `infos.ignore_snapshot_if_db_exists` | `true` if `--ignore-snapshot-if-db-exists` is activated, otherwise `false` | true | `infos.http_addr` | `true` if `--http-addr`/`MEILI_HTTP_ADDR` is specified, otherwise `false` | 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, otherwise `false` | false | `infos.ssl_cert_path` | `true` if `--ssl-cert-path`/`MEILI_SSL_CERT_PATH` is specified, otherwise `false` | false | `infos.ssl_key_path` | `true` if `--ssl-key-path`/`MEILI_SSL_KEY_PATH` is specified, otherwise `false` | false | `infos.ssl_ocsp_path` | `true` if `--ssl-ocsp-path`/`MEILI_SSL_OCSP_PATH` is specified, otherwise `false` | 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, otherwise `false` | false | `infos.ssl_tickets` | `true` if `--ssl-tickets`/`MEILI_SSL_TICKETS` is specified, otherwise `false` | 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` | Users can tell us on which provider Meilisearch is hosted by filling the `MEILI_SERVER_PROVIDER` environment variable. This is also filled by Meilisearch cloud deploy scripts | 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 cancelled 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, otherwise `false` | 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, otherwise `false` | false | `filter.with_geoRadius` | `true` if the filter rule `_geoRadius` is specified, otherwise `false` | false | `filter.most_used_syntax` | Most used filter syntax among all search requests containing the `filter` parameter | string | `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, otherwise `false` | false | `formatting.highlight_post_tag` | `true` if `highlightPostTag` is specified, otherwise `false` | false | `formatting.max_attributes_to_crop` | Maximum number of attributes to crop | 100 | `formatting.crop_length` | `true` if `cropLength` is specified, otherwise `false` | false | `formatting.crop_marker` | `true` if `cropMarker` is specified, otherwise `false` | false | `formatting.show_matches_position` | `true` if `showMatchesPosition` is used in this batch, otherwise `false` | false | `facets.avg_facets_number` | Average number of facets | 10 | `primary_key` | Name of primary key when explicitly set as part of document addition, document update, index creation, or index update. 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, otherwise `false` | 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, otherwise `false` | true | `filterable_attributes.total` | Number of filterable attributes | 3 | `filterable_attributes.has_geo` | `true` if `_geo` is set as a filterable attribute, otherwise `false` | false | `searchable_attributes.total` | Number of searchable attributes | 4 | `searchable_attributes.with_wildcard` | `true` if `*` is specified as a searchable attribute, otherwise `false` | false | `per_task_uid` | `true` if a `uids` is used to fetch a particular task resource, otherwise `false` | true | `filtered_by_uid` | `true` if tasks are filtered by the `uids` query parameter, otherwise `false` | false | `filtered_by_index_uid` | `true` if tasks are filtered by the `indexUids` query parameter, otherwise `false` | false | `filtered_by_type` | `true` if tasks are filtered by the `types` query parameter, otherwise `false` | false | `filtered_by_status` | `true` if tasks are filtered by the `statuses` query parameter, otherwise `false` | false | `filtered_by_canceled_by` | `true` if tasks are filtered by the `canceledBy` query parameter, otherwise `false` | false | `filtered_by_before_enqueued_at` | `true` if tasks are filtered by the `beforeEnqueuedAt` query parameter, otherwise `false` | false | `filtered_by_after_enqueued_at` | `true` if tasks are filtered by the `afterEnqueuedAt` query parameter, otherwise `false` | false | `filtered_by_before_started_at` | `true` if tasks are filtered by the `beforeStartedAt` query parameter, otherwise `false` | false | `filtered_by_after_started_at` | `true` if tasks are filtered by the `afterStartedAt` query parameter, otherwise `false` | false | `filtered_by_before_finished_at` | `true` if tasks are filtered by the `beforeFinishedAt` query parameter, otherwise `false` | false | `filtered_by_after_finished_at` | `true` if tasks are filtered by the `afterFinishedAt` query parameter, otherwise `false` | false | `typo_tolerance.enabled` | `true` if typo tolerance is enabled, otherwise `false` | true | `typo_tolerance.disable_on_attributes` | `true` if at least one value is defined for `disableOnAttributes`, otherwise `false` | false | `typo_tolerance.disable_on_words` | `true` if at least one value is defined for `disableOnWords`, otherwise `false` | 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, otherwise `false` | false | `distinct` | Boolean indicating whether a distinct was specified in an aggregated list of requests. | true | `proximity_precision.set` | `true` if the setting has been manually set, otherwise `false`. | fals` | `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, otherwise `false` | 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, otherwise `false` | 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, otherwise `false` | 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`/`MEILI_EXPERIMENTAL_ENABLE_METRICS` is specified, otherwise `false` | false | `infos.experimental_replication_parameters` | `true` if `--experimental-replication-parameters`/`MEILI_EXPERIMENTAL_REPLICATION_PARAMETERS` is specified at launch, otherwise `false` | `false` | `infos.experimental_reduce_indexing_memory_usage` | `true` if `--experimental-reduce-indexing-memory-usage`/`MEILI_EXPERIMENTAL_REDUCE_INDEXING_MEMORY_USAGE` is specified at launch, otherwise `false` | `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`/`MEILI_EXPERIMENTAL_ENABLE_LOGS_ROUTE` is specified at launch, otherwise `false` | `false` | `infos.gpu_enabled` | `true` if Meilisearch was compiled with CUDA support, otherwise `false` | `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, otherwise `false` | false | `per_filter` | `true` if `POST /indexes/:indexUid/documents/fetch`, `GET /indexes/:indexUid/documents/`, or `POST /indexes/:indexUid/documents/delete` endpoints were used, otherwise `false` | false | `clear_all` | `true` if `DELETE /indexes/:indexUid/documents` endpoint was used, otherwise `false` | false | `per_batch` | `true` if the `POST /indexes/:indexUid/documents/delete-batch` endpoint was used, otherwise `false` | 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, otherwise `false` | false | `faceting.sort_facet_values_by_star_count` | `true` if all fields are set to be sorted by count, otherwise `false` | 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, otherwise `false` | true | `scoring.show_ranking_score_details` | `true` if `showRankingScoreDetails` was used in the aggregated event, otherwise `false` | true | `scoring.ranking_score_threshold` | Boolean indicating whether a rankingScoreThreshold was specified in an aggregated list of requests | true | | `vector_store` | `true` if the vector store feature is enabled, otherwise `false` | true | `attributes_to_search_on.total_number_of_uses` | `true` if the vector store feature is enabled, otherwise `false` | true | `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, otherwise `false` | true | `hybrid.semantic_ratio` | `true` if semanticRatio was used in this batch, otherwise false | false | `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, otherwise `false` | `false` | `infos.experimental_search_queue_size` | Size of the search queue | 750 | `locales` | List of locales used with `/search` and `/settings` routes | [”fra”, “eng”] | `federation.use_federation` | `true` if at least one of the multi-search requests from the last aggregate contained a non-null top-level federation object | `false` --- title: Language — Meilisearch documentation description: 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. --- ## Language 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. --- title: Versioning policy — Meilisearch documentation description: This article describes the system behind Meilisearch's SDK and engine version numbering and compatibility. --- ## Versioning policy 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. --- title: Contributing to our documentation — Meilisearch documentation description: The Meilisearch documentation is open-source. Learn how to help make it even better. sidebarDepth: 3 --- ## Contributing to our documentation 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 --- title: Overview — Meilisearch API reference description: 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. --- ## API reference 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 You can get the [Meilisearch OpenAPI specifications here](https://github.com/meilisearch/open-api/blob/main/open-api.json). ### 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 # replace the MASTER_KEY placeholder with your master key curl \ -X GET 'MEILISEARCH_URL/keys' \ -H 'Authorization: Bearer MASTER_KEY' ``` ```js const client = new MeiliSearch({ host: 'http://localhost:7700', apiKey: 'masterKey' }) client.getKeys() ``` ```py client = Client('http://localhost:7700', 'masterKey') client.get_keys() ``` ```php $client = new Client('http://localhost:7700', 'masterKey'); $client->getKeys(); ``` ```java Client client = new Client(new Config("http://localhost:7700", "masterKey")); client.getKeys(); ``` ```ruby client = MeiliSearch::Client.new('http://localhost:7700', 'masterKey') client.keys ``` ```go client := meilisearch.New("http://localhost:7700", meilisearch.WithAPIKey("masterKey")) client.GetKeys(nil); ``` ```csharp MeilisearchClient client = new MeilisearchClient("http://localhost:7700", "masterKey"); var keys = await client.GetKeysAsync(); ``` ```rust let client = Client::new("http://localhost:7700", Some("masterKey")); let keys = client .get_keys() .await .unwrap(); ``` ```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 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 \ -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). --- title: Indexes — Meilisearch API reference description: The /indexes route allows you to create, manage, and delete your indexes. --- ## Indexes 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 \ -X GET 'MEILISEARCH_URL/indexes?limit=3' ``` ```js client.getIndexes({ limit: 3 }) ``` ```py client.get_indexes({'limit': 3}) ``` ```php $client->getIndexes((new IndexesQuery())->setLimit(3)); ``` ```java IndexesQuery query = new IndexesQuery().setLimit(3); client.getIndexes(query); ``` ```ruby client.indexes(limit: 3) ``` ```go client.GetIndexes(&meilisearch.IndexesQuery{ Limit: 3, }) ``` ```csharp await client.GetAllIndexesAsync(new IndexesQuery { Limit = 3 }); ``` ```rust let mut indexes = IndexesQuery::new(&client) .with_limit(3) .execute() .await .unwrap(); ``` ```swift client.getIndexes { (result) in switch result { case .success(let indexes): print(indexes) case .failure(let error): print(error) } } ``` ```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 \ -X GET 'MEILISEARCH_URL/indexes/movies' ``` ```js client.index('movies').getRawInfo() ``` ```py client.get_index('movies') ``` ```php $client->index('movies')->fetchRawInfo(); ``` ```java client.getIndex("movies"); ``` ```ruby client.fetch_index('movies') ``` ```go client.GetIndex("movies") ``` ```csharp await client.GetIndexAsync("movies"); ``` ```rust let movies: Index = client .get_index("movies") .await .unwrap(); ``` ```swift client.getIndex("movies") { (result) in switch result { case .success(let index): print(index) case .failure(let error): print(error) } } ``` ```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 \ -X POST 'MEILISEARCH_URL/indexes' \ -H 'Content-Type: application/json' \ --data-binary '{ "uid": "movies", "primaryKey": "id" }' ``` ```js client.createIndex('movies', { primaryKey: 'id' }) ``` ```py client.create_index('movies', {'primaryKey': 'id'}) ``` ```php $client->createIndex('movies', ['primaryKey' => 'id']); ``` ```java client.createIndex("movies", "id"); ``` ```ruby client.create_index('movies', primary_key: 'id') ``` ```go client.CreateIndex(&meilisearch.IndexConfig{ Uid: "movies", PrimaryKey: "id", }) ``` ```csharp TaskInfo task = await client.CreateIndexAsync("movies", "id"); ``` ```rust client.create_index("movies", Some("id")) .await .unwrap(); ``` ```swift client.createIndex(uid: "movies", primaryKey: "id") { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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. It is not possible to change an index's `uid`. #### 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 | #### Example ```bash curl \ -X PATCH 'MEILISEARCH_URL/indexes/movies' \ -H 'Content-Type: application/json' \ --data-binary '{ "primaryKey": "id" }' ``` ```js client.updateIndex('movies', { primaryKey: 'id' }) ``` ```py client.index('movies').update(primary_key='id') ``` ```php $client->updateIndex('movies', ['primaryKey' => 'id']); ``` ```java client.updateIndex("movies", "id"); ``` ```ruby client.index('movies').update(primary_key: 'movie_id') ``` ```go client.Index("movies").UpdateIndex("id") ``` ```csharp TaskInfo task = await client.UpdateIndexAsync("movies", "id"); ``` ```rust let task = IndexUpdater::new("movies", &client) .with_primary_key("movie_review_id") .execute() .await .unwrap(); ``` ```swift client.index("movies").update(primaryKey: "id") { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X DELETE 'MEILISEARCH_URL/indexes/movies' ``` ```js client.deleteIndex('movies') ``` ```py client.delete_index('movies') // OR client.index('movies').delete() ``` ```php $client->deleteIndex('movies'); ``` ```java client.deleteIndex("movies"); ``` ```ruby client.delete_index('movies') ``` ```go client.DeleteIndex("movies") // OR client.Index("movies").Delete() ``` ```csharp await client.DeleteIndexAsync("movies"); ``` ```rust client.index("movies") .delete() .await .unwrap(); ``` ```swift client.index("movies").delete { (result) in switch result { case .success: print("Index deleted") case .failure(let error): print(error) } } ``` ```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.** However, a single request can swap as many index pairs as you wish. Swapping indexes is an atomic transaction: **either all indexes are successfully swapped, or none are.** 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. Each object has only one key: `indexes`. | Name | Type | Default value | Description | | :------------- | :--------------- | :------------ | :----------------------------------------- | | **`indexes`*** | Array of strings | N/A | Array of the two `indexUid`s to be swapped | 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. 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. #### Example ```bash curl \ -X POST 'MEILISEARCH_URL/swap-indexes' \ -H 'Content-Type: application/json' \ --data-binary '[ { "indexes": [ "indexA", "indexB" ] }, { "indexes": [ "indexX", "indexY" ] } ]' ``` ```js client.swapIndexes([ { 'indexes': ['indexA', 'indexB'] }, { 'indexes': ['indexX', 'indexY'] } ]) ``` ```py client.swap_indexes([{'indexes': ['indexA', 'indexB']}, {'indexes': ['indexX', 'indexY']}]) ``` ```php $client->swapIndexes([['indexA', 'indexB'], ['indexX', 'indexY']]); ``` ```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 client.swap_indexes(['indexA', 'indexB'], ['indexX', 'indexY']) ``` ```go client.SwapIndexes([]SwapIndexesParams{ {Indexes: []string{"indexA", "indexB"}}, {Indexes: []string{"indexX", "indexY"}}, }) ``` ```csharp await client.SwapIndexesAsync(new List { new IndexSwap("indexA", "indexB"), new IndexSwap("indexX", "indexY") } }); ``` ```rust client.swap_indexes([ &SwapIndexes { indexes: ( "indexA".to_string(), "indexB".to_string(), ), }, &SwapIndexes { indexes: ( "indexX".to_string(), "indexY".to_string(), ), }]) ``` ```swift let task = try await self.client.swapIndexes([ ("indexA", "indexB"), ("indexX", "indexY") ]) ``` ```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). --- title: Documents — Meilisearch API reference description: The /documents route allows you to create, manage, and delete documents. --- ## Documents 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 | 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 | #### Example ```bash 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 }' ``` ```js client.index('books').getDocuments({ filter: '(rating > 3 AND (genres = Adventure OR genres = Fiction)) AND language = English', fields: ['title', 'genres', 'rating', 'language'], limit: 3 }) ``` ```py 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 $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 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 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 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 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 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 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 | #### 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 | #### Example ```bash curl \ -X GET 'MEILISEARCH_URL/indexes/movies/documents?limit=2&filter=genres=action' ``` ```js client.index('movies').getDocuments({ limit: 2, filter: 'genres = action' }) ``` ```py client.index('movies').get_documents({'limit':2, 'filter': 'genres=action'}) ``` ```php $client->index('movies')->getDocuments((new DocumentsQuery())->setFilter('genres = action')->setLimit(2)); ``` ```java DocumentsQuery query = new DocumentsQuery().setLimit(2).setFilter(new String[] {"genres = action"}); client.index("movies").getDocuments(query, TargetClassName.class); ``` ```ruby client.index('movies').get_documents(limit: 2, filter: 'genres = action') ``` ```go var result meilisearch.DocumentsResult client.Index("movies").GetDocuments(&meilisearch.DocumentsQuery{ Limit: 2, Filter: "genres = action", }, &result) ``` ```csharp await client.Index("movies").GetDocumentsAsync(new DocumentsQuery() { Limit = 2, Filter = "genres = action" }); ``` ```rust let index = client.index("movies"); let documents: DocumentsResults = DocumentsQuery::new(&index) .with_filter("genres = action") .with_limit(2) .execute::() .await .unwrap(); ``` ```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 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 \ -X GET 'MEILISEARCH_URL/indexes/movies/documents/25684?fields=id,title,poster,release_date' ``` ```js client .index('movies') .getDocument(25684, { fields: ['id', 'title', 'poster', 'release_date'] }) ``` ```py client.index('movies').get_document(25684, { 'fields': ['id', 'title', 'poster', 'release_date'] }) ``` ```php $client->index('movies')->getDocument(25684, ['id', 'title', 'poster', 'release_date']); ``` ```java DocumentQuery query = new DocumentQuery().setFields(new String[] {"id", "title", "poster", "release_date"}); client.index("movies").getDocument("25684", query); ``` ```ruby client.index('movies').document(25684, fields: ['id', 'title', 'poster', 'release_date']) ``` ```go var a interface{} client.Index("movies").GetDocument("25684",&meilisearch.DocumentQuery{ Fields: []string{"id", "title", "poster", "release_date"}, }, &a) ``` ```csharp await client.Index("movies").GetDocumentAsync(25684, new List { "id", "title", "poster", "release_date" }); ``` ```rust let index = client .index("movies"); let document = DocumentQuery::new(&index) .with_fields(["id", "title", "poster", "release_date"]) .execute::("25684") .await .unwrap(); ``` ```swift client.index("movies").getDocument(25684) { (result: Result) in switch result { case .success(let movie): print(movie) case .failure(let error): print(error) } } ``` ```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 \ -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" } ]' ``` ```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' }]) ``` ```py 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 $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 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 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 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) ``` ```csharp 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 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 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 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 \ -X PUT 'MEILISEARCH_URL/indexes/movies/documents' \ -H 'Content-Type: application/json' \ --data-binary '[ { "id": 287947, "title": "Shazam ⚡️", "genres": "comedy" } ]' ``` ```js client.index('movies').updateDocuments([{ id: 287947, title: 'Shazam ⚡️', genres: 'comedy' }]) ``` ```py client.index('movies').update_documents([{ 'id': 287947, 'title': 'Shazam ⚡️', 'genres': 'comedy' }]) ``` ```php $client->index('movies')->updateDocuments([ [ 'id' => 287947, 'title' => 'Shazam ⚡️', 'genres' => 'comedy' ] ]); ``` ```java client.index("movies").updateDocuments("[{ + "\"id\": 287947," + "\"title\": \"Shazam ⚡️\"," + "\"genres\": \"comedy\"" + "}]" ); ``` ```ruby client.index('movies').update_documents([ { id: 287947, title: 'Shazam ⚡️', genres: 'comedy' } ]) ``` ```go documents := []map[string]interface{}{ { "id": 287947, "title": "Shazam ⚡️", "genres": "comedy", }, } client.Index("movies").UpdateDocuments(documents) ``` ```csharp var movie = new[] { new Movie { Id = "287947", Title = "Shazam ⚡️", Genres = "comedy" } }; await index.UpdateDocumentsAsync(movie); ``` ```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 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 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 \ -X DELETE 'MEILISEARCH_URL/indexes/movies/documents' ``` ```js client.index('movies').deleteAllDocuments() ``` ```py client.index('movies').delete_all_documents() ``` ```php $client->index('movies')->deleteAllDocuments(); ``` ```java client.index("movies").deleteAllDocuments(); ``` ```ruby client.index('movies').delete_all_documents ``` ```go client.Index("movies").DeleteAllDocuments() ``` ```csharp await client.Index("movies").DeleteAllDocumentsAsync(); ``` ```rust let task: TaskInfo = client .index("movies") .delete_all_documents() .await .unwrap(); ``` ```swift client.index("movies").deleteAllDocuments() { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X DELETE 'MEILISEARCH_URL/indexes/movies/documents/25684' ``` ```js client.index('movies').deleteDocument(25684) ``` ```py client.index('movies').delete_document(25684) ``` ```php $client->index('movies')->deleteDocument(25684); ``` ```java client.index("movies").deleteDocument("25684"); ``` ```ruby client.index('movies').delete_document(25684) ``` ```go client.Index("movies").DeleteDocument("25684") ``` ```csharp await client.Index("movies").DeleteOneDocumentAsync("25684"); ``` ```rust let task: TaskInfo = client .index("movies") .delete_document(25684) .await .unwrap(); ``` ```swift client.index("movies").deleteDocument("25684") { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X POST MEILISEARCH_URL/indexes/movies/documents/delete \ -H 'Content-Type: application/json' \ --data-binary '{ "filter": "genres = action OR genres = adventure" }' ``` ```js client.index('movies').deleteDocuments({ filter: 'genres = action OR genres = adventure' }) ``` ```py client.index('movies').delete_documents(filter='genres=action OR genres=adventure') ``` ```php $client->index('movies')->deleteDocuments(['filter' => 'genres = action OR genres = adventure']); ``` ```java String filter = "genres = action OR genres = adventure"; client.index("movies").deleteDocumentsByFilter(filter); ``` ```ruby client.index('movies').delete_documents(filter: 'genres = action OR genres = adventure') ``` ```go client.Index("movies").DeleteDocumentsByFilter("genres=action OR genres=adventure") ``` ```csharp await client.Index("movies").DeleteDocumentsAsync(new DeleteDocumentsQuery() { Filter = "genres = action OR genres = adventure" }); ``` ```rust let index = client.index("movies"); let task = DocumentDeletionQuery::new(&index) .with_filter("genres = action OR genres = adventure") .execute() .await .unwrap(); ``` ```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 This endpoint will be deprecated in the near future. Consider using POST `/indexes/{index_uid}/documents/delete` instead . 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/documents/delete-batch' \ -H 'Content-Type: application/json' \ --data-binary '[ 23488, 153738, 437035, 363869 ]' ``` ```js client.index('movies').deleteDocuments([23488, 153738, 437035, 363869]) ``` ```py client.index('movies').delete_documents([23488, 153738, 437035, 363869]) ``` ```php $client->index('movies')->deleteDocuments([23488, 153738, 437035, 363869]); ``` ```java client.index("movies").deleteDocuments(Arrays.asList(new String[] { "23488", "153738", "437035", "363869" })); ``` ```ruby client.index('movies').delete_documents([23488, 153738, 437035, 363869]) ``` ```go client.Index("movies").DeleteDocuments([]string{ "23488", "153738", "437035", "363869", }) ``` ```csharp await client.Index("movies").DeleteDocumentsAsync(new[] { "23488", "153738", "437035", "363869" }); ``` ```rust let task: TaskInfo = client .index("movies") .delete_documents(&[23488, 153738, 437035, 363869]) .await .unwrap(); ``` ```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). --- title: Search — Meilisearch API reference description: 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. sidebarDepth: 3 --- ## Search 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`](/reference/api/search#filter)** | [String](/learn/filtering_and_sorting/filter_expression_reference) | `null` | Filter queries by an attribute's value | | **[`facets`](#facets)** | Array of strings | `null` | Display the count of matches per facet | | **[`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-experimental)** | Object | `null` | Return results based on query keywords and meaning | | **[`vector`](#vector-experimental)** | Array of numbers | `null` | Search using a custom query vector | | **[`retrieveVectors`](#display-_vectors-in-response-experimental)** | Boolean | `false` | Return document vector data | | **[`locales`](#query-locales)** | Array of strings | `null` | Explicitly specify languages used in a query | [Learn more about how to use each search parameter](#search-parameters). #### 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "american ninja" }' ``` ```js client.index('movies').search('American ninja') ``` ```py client.index('movies').search('American ninja') ``` ```php $client->index('movies')->search('american ninja'); ``` ```java client.index("movies").search("American ninja"); ``` ```ruby client.index('movies').search('american ninja') ``` ```go client.Index("movies").Search("american ninja", &meilisearch.SearchRequest{}) ``` ```csharp await client.Index("movies").SearchAsync("American ninja"); ``` ```rust let results: SearchResults = client .index("movies") .search() .with_query("American ninja") .execute() .await .unwrap(); ``` ```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 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`](/reference/api/search#filter)** | [String](/learn/filtering_and_sorting/filter_expression_reference) | `null` | Filter queries by an attribute's value | | **[`facets`](#facets)** | Array of strings | `null` | Display the count of matches per facet | | **[`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-experimental)** | Object | `null` | Return results based on query keywords and meaning | | **[`vector`](#vector-experimental)** | Array of numbers | `null` | Search using a custom query vector | | **[`retrieveVectors`](#display-_vectors-in-response-experimental)** | Boolean | `false` | Return document vector data | | **[`locales`](#query-locales)** | Array of strings | `null` | Explicitly specify languages used in a query | [Learn more about how to use each search parameter](#search-parameters). #### 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 \ -X GET 'MEILISEARCH_URL/indexes/movies/search?q=american%20ninja' ``` ```js client.index('movies').searchGet('American ninja') ``` ```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). #### Overview | 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)** | 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 the attribute defined as distinct. | | **[`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 | | **[`attributesToSearchOn`](#customize-attributes-to-search-on-at-search-time)** | Array of strings | `["*"]` | Restrict search to the specified attributes | | **[`hybrid`](#hybrid-search-experimental)** | Object | `null` | Return results based on query keywords and meaning | | **[`vector`](#vector-experimental)** | Array of numbers | `null` | Search using a custom query vector | | **[`retrieveVectors`](#display-_vectors-in-response-experimental)** | Boolean | `false` | Return document vector data | | **[`locales`](#query-locales)** | Array of strings | `null` | Explicitly specify languages used in a query | #### 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "shifu" }' ``` ```js client.index('movies').search('shifu') ``` ```py client.index('movies').search('shifu') ``` ```php $client->index('movies')->search('shifu'); ``` ```java client.index("movies").search("shifu"); ``` ```ruby client.index('movies').search('shifu') ``` ```go resp, err := client.Index("movies").Search("shifu", &meilisearch.SearchRequest{}) ``` ```csharp await client.Index("movies").SearchAsync("shifu"); ``` ```rust let results: SearchResults = client .index("movies") .search() .with_query("shifu") .execute() .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "\"african american\" horror" }' ``` ```js client.index('movies') .search('"african american" horror') ``` ```py client.index('movies').search('"african american" horror') ``` ```php $client->index('movies')->search('"african american" horror'); ``` ```java client.index("movies").search("\"african american\" horror"); ``` ```ruby client.index('movies').search('"african american" horror') ``` ```go resp, err := client.Index("movies").Search("\"african american\" horror", &meilisearch.SearchRequest{}) ``` ```csharp await client.Index("movies").SearchAsync("\"african american\" horror"); ``` ```rust let results: SearchResults = client .index("movies") .search() .with_query("\"african american\" horror") .execute() .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "-escape" }' ``` ```js client.index('movies').search('-escape') ``` ```php $client->index('movies')->search('-escape'); ``` Negative search can be used together with phrase search: ```bash curl \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "-\"escape room\"" }' ``` ```js client.index('movies').search('-"escape"') ``` ```php $client->index('movies')->search('-"escape"'); ``` #### 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "shifu", "offset": 1 }' ``` ```js client.index('movies').search('shifu', { offset: 1 }) ``` ```py client.index('movies').search('shifu', { 'offset': 1 }) ``` ```php $client->index('movies')->search('shifu', ['offset' => 1]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("shifu").offset(1).build(); client.index("movies").search(searchRequest); ``` ```ruby client.index('movies').search('shifu', { offset: 1 }) ``` ```go resp, err := client.Index("movies").Search("shifu", &meilisearch.SearchRequest{ Offset: 1, }) ``` ```csharp var sq = new SearchQuery { Offset = 1 }; var result = await client.Index("movies").SearchAsync("shifu", sq); if(result is SearchResult pagedResults) { } ``` ```rust let results: SearchResults = client .index("movies") .search() .with_query("shifu") .with_offset(1) .execute() .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "shifu", "limit": 2 }' ``` ```js client.index('movies').search('shifu', { limit: 2 }) ``` ```py client.index('movies').search('shifu', { 'limit': 2 }) ``` ```php $client->index('movies')->search('shifu', ['limit' => 2]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("shifu").limit(2).build(); client.index("movies").search(searchRequest); ``` ```ruby client.index('movies').search('shifu', { limit: 2 }) ``` ```go resp, err := client.Index("movies").Search("shifu", &meilisearch.SearchRequest{ Limit: 2, }) ``` ```csharp var sq = new SearchQuery { Limit = 2 }; var result = await client.Index("movies").SearchAsync("shifu", sq); if(result is SearchResult pagedResults) { } ``` ```rust let results: SearchResults = client .index("movies") .search() .with_query("shifu") .with_limit(2) .execute() .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "", "hitsPerPage": 15 }' ``` ```js client.index('movies').search('', { hitsPerPage: 15 }) ``` ```py client.index('movies').search('', {'hitsPerPage': 15}) ``` ```php $client->index('movies')->search('', ['hitsPerPage' => 15]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("").hitsPerPage(15).build(); SearchResultPaginated searchResult = client.index("movies").search(searchRequest); ``` ```ruby client.index('movies').search('', hits_per_page: 15) ``` ```go client.Index("movies").Search("", &meilisearch.SearchRequest{ HitsPerPage: 15, }) ``` ```csharp var result = await client.Index("movies").SearchAsync("", new SearchQuery { HitsPerPage = 15 }); if(result is PaginatedSearchResult pagedResults) { } ``` ```rust client.index("movies").search().with_hits_per_page(15).execute().await?; ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "", "page": 2 }' ``` ```js client.index('movies').search('', { page: 2 }) ``` ```py client.index('movies').search('', {'page': 2}) ``` ```php $client->index('movies')->search('', ['page' => 2]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("").page(15).build(); SearchResultPaginated searchResult = client.index("movies").search(searchRequest); ``` ```ruby client.index('movies').search('', page: 2) ``` ```go client.Index("movies").Search("", &meilisearch.SearchRequest{ Page: 2, }) ``` ```csharp var result = await client.Index("movies").SearchAsync("", new SearchQuery { Page = 2 }); if(result is PaginatedSearchResult pagedResults) { } ``` ```rust client.index("movies").search().with_page(2).execute().await?; ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "thriller", "filter": [ [ "genres = Horror", "genres = Mystery" ], "director = \"Jordan Peele\"" ] }' ``` ```js client.index('movies') .search('thriller', { filter: [['genres = Horror', 'genres = Mystery'], 'director = "Jordan Peele"'] }) ``` ```py client.index('movies').search('thriller', { 'filter': [['genres = Horror', 'genres = Mystery'], 'director = "Jordan Peele"'] }) ``` ```php $client->index('movies')->search('thriller', [ 'filter' => [['genres = Horror', 'genres = Mystery'], 'director = "Jordan Peele"'] ]); ``` ```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 client.index('movies').search('thriller', { filter: [['genres = Horror', 'genres = Mystery'], 'director = "Jordan Peele"'] }) ``` ```go resp, err := client.Index("movies").Search("thriller", &meilisearch.SearchRequest{ Filter: [][]string{ []string{"genres = Horror", "genres = Mystery"}, []string{"director = \"Jordan Peele\""}, }, }) ``` ```csharp var sq = new SearchQuery { Filter = "(genre = 'Horror' AND genre = 'Mystery') OR director = 'Jordan Peele'" }; await client.Index("movies").SearchAsync("thriller", sq); ``` ```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 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 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 \ -X POST 'MEILISEARCH_URL/indexes/restaurants/search' \ -H 'Content-type:application/json' \ --data-binary '{ "filter": "_geoRadius(45.472735, 9.184019, 2000)" }' ``` ```js client.index('restaurants').search('', { filter: ['_geoRadius(45.472735, 9.184019, 2000)'], }) ``` ```py client.index('restaurants').search('', { 'filter': '_geoRadius(45.472735, 9.184019, 2000)' }) ``` ```php $client->index('restaurants')->search('', [ 'filter' => '_geoRadius(45.472735, 9.184019, 2000)' ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("").filter(new String[] {"_geoRadius(45.472735, 9.184019, 2000)"}).build(); client.index("restaurants").search(searchRequest); ``` ```ruby client.index('restaurants').search('', { filter: '_geoRadius(45.472735, 9.184019, 2000)' }) ``` ```go resp, err := client.Index("restaurants").Search("", &meilisearch.SearchRequest{ Filter: "_geoRadius(45.472735, 9.184019, 2000)", }) ``` ```csharp SearchQuery filters = new SearchQuery() { Filter = "_geoRadius(45.472735, 9.184019, 2000)" }; var restaurants = await client.Index("restaurants").SearchAsync("", filters); ``` ```rust let results: SearchResults = client .index("restaurants") .search() .with_filter("_geoRadius(45.472735, 9.184019, 2000)") .execute() .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/restaurants/search' \ -H 'Content-type:application/json' \ --data-binary '{ "filter": "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])" }' ``` ```js client.index('restaurants').search('', { filter: ['_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])'], }) ``` ```py client.index('restaurants').search('Batman', { 'filter': '_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])' }) ``` ```php $client->index('restaurants')->search('', [ 'filter' => '_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])' ]); ``` ```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 client.index('restaurants').search('', { filter: ['_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])'] }) ``` ```go client.Index("restaurants").Search("", &meilisearch.SearchRequest{ Filter: "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])", }) ``` ```csharp SearchQuery filters = new SearchQuery() { Filter = "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])" }; var restaurants = await client.Index("restaurants").SearchAsync("restaurants", filters); ``` ```rust let results: SearchResults = client .index("restaurants") .search() .with_filter("_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])") .execute() .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/movie_ratings/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "Batman", "facets": ["genres", "rating"] }' ``` ```js client.index('movie_ratings').search('Batman', { facets: ['genres', 'rating'] }) ``` ```py client.index('movie_ratings').search('Batman', { 'facets': ['genres', 'rating'] }) ``` ```php $client->index('movie_ratings')->search('Batman', [ 'facets' => ['genres', 'rating'] ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("Batman").facets(new String[] { "genres", "rating" }).build(); client.index("movies").search(searchRequest); ``` ```ruby client.index('movie_ratings').search('Batman', { facets: ['genres', 'rating'] }) ``` ```go client.Index("movie_ratings").Search("Batman", &meilisearch.SearchRequest{ Facets: []string{ "genres", "rating", }, }) ``` ```csharp var sq = new SearchQuery { Facets = new string[] { "genres", "rating" } }; await client.Index("movie_ratings").SearchAsync("Batman", sq); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/INDEX_NAME/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "QUERY TERMS", "distinct": "ATTRIBUTE_A" }' ``` ```js client.index('INDEX_NAME').search('QUERY TERMS', { distinct: 'ATTRIBUTE_A' }) ``` ```py client.index('INDEX_NAME').search('QUERY_TERMS', { distinct: 'ATTRIBUTE_A' }) ``` ```php $client->index('INDEX_NAME')->search('QUERY TERMS', [ 'distinct' => 'ATTRIBUTE_A' ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("QUERY TERMS").distinct("ATTRIBUTE_A").build(); client.index("INDEX_NAME").search(searchRequest); ``` ```ruby client.index('INDEX_NAME').search('QUERY TERMS', { distinct: 'ATTRIBUTE_A' }) ``` ```go client.Index("INDEX_NAME").Search("QUERY TERMS", &meilisearch.SearchRequest{ Distinct: "ATTRIBUTE_A", }) ``` ```csharp var params = new SearchQuery() { Distinct = "ATTRIBUTE_A" }; await client.Index("INDEX_NAME").SearchAsync("QUERY TERMS", params); ``` ```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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "shifu", "attributesToRetrieve": [ "overview", "title" ] }' ``` ```js client.index('movies').search('shifu', { attributesToRetrieve: ['overview', 'title'] }) ``` ```py client.index('movies').search('shifu', { 'attributesToRetrieve': ['overview', 'title'] }) ``` ```php $client->index('movies')->search('shifu', [ 'attributesToRetrieve' => ['overview', 'title'] ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("a").attributesToRetrieve(new String[] {"overview", "title"}).build(); client.index("movies").search(searchRequest); ``` ```ruby client.index('movies').search('shifu', { attributes_to_retrieve: ['overview', 'title'] }) ``` ```go resp, err := client.Index("movies").Search("shifu", &meilisearch.SearchRequest{ AttributesToRetrieve: []string{"overview", "title"}, }) ``` ```csharp var sq = new SearchQuery { AttributesToRetrieve = new[] {"overview", "title"} }; await client.Index("movies").SearchAsync("shifu", sq); ``` ```rust let results: SearchResults = client .index("movies") .search() .with_query("shifu") .with_attributes_to_retrieve(Selectors::Some(&["overview", "title"])) .execute() .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "shifu", "attributesToCrop": ["overview"], "cropLength": 5 }' ``` ```js client.index('movies').search('shifu', { attributesToCrop: ['overview'], cropLength: 5 }) ``` ```py client.index('movies').search('shifu', { 'attributesToCrop': ['overview'], 'cropLength': 5 }) ``` ```php $client->index('movies')->search('shifu', [ 'attributesToCrop' => ['overview'], 'cropLength' => 5 ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder() .q("shifu") .attributesToCrop(new String[] {"overview"}) .cropLength(5) .build(); client.index("movies").search(searchRequest); ``` ```ruby client.index('movies').search('shifu', { attributes_to_crop: ['overview'], crop_length: 5 }) ``` ```go resp, err := client.Index("movies").Search("shifu", &meilisearch.SearchRequest{ AttributesToCrop: []string{"overview"}, CropLength: 5, }) ``` ```csharp var sq = new SearchQuery { AttributesToCrop = new[] {"overview"}, CropLength = 5 }; await client.Index("movies").SearchAsync("shifu", sq); ``` ```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 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 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "shifu", "cropMarker": "[…]", "attributesToCrop": ["overview"] }' ``` ```js client.index('movies').search('shifu', { attributesToCrop: ['overview'], cropMarker: '[…]' }) ``` ```py client.index('movies').search('shifu', { 'attributesToCrop': ['overview'], 'cropMarker': '[…]' }) ``` ```php $client->index('movies')->search('shifu', [ 'attributesToCrop' => ['overview'], 'cropMarker' => '[…]' ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder() .q("shifu") .attributesToCrop(new String[] {"overview"}) .cropMarker("[…]") .build(); client.index("movies").search(searchRequest); ``` ```ruby client.index('movies').search('shifu', { attributes_to_crop: ['overview'], crop_marker: '[…]' }) ``` ```go resp, err := client.Index("movies").Search("shifu", &meilisearch.SearchRequest{ AttributesToCrop: []string{"overview"}, CropMarker: "[…]", }) ``` ```csharp var sq = new SearchQuery { AttributesToCrop = new[] {"overview"}, CropMarker = "[...]" }; await client.Index("movies").SearchAsync("shifu", sq); ``` ```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 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 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "winter feast", "attributesToHighlight": ["overview"] }' ``` ```js client.index('movies').search('winter feast', { attributesToHighlight: ['overview'] }) ``` ```py client.index('movies').search('winter feast', { 'attributesToHighlight': ['overview'] }) ``` ```php $client->index('movies')->search('winter feast', [ 'attributesToHighlight' => ['overview'] ]); ``` ```java SearchRequest searchRequest = new SearchRequest("winter feast").setAttributesToHighlight(new String[] {"overview"}); client.index("movies").search(searchRequest); ``` ```ruby client.index('movies').search('winter feast', { attributes_to_highlight: ['overview'] }) ``` ```go resp, err := client.Index("movies").Search("winter feast", &meilisearch.SearchRequest{ AttributesToHighlight: []string{"overview"}, }) ``` ```csharp var sq = new SearchQuery { AttributesToHighlight = new[] {"overview"} }; await client.Index("movies").SearchAsync("winter feast", sq); ``` ```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 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 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "winter feast", "attributesToHighlight": ["overview"], "highlightPreTag": "", "highlightPostTag": "" }' ``` ```js client.index('movies').search('winter feast', { attributesToHighlight: ['overview'], highlightPreTag: '', highlightPostTag: '' }) ``` ```py client.index('movies').search('winter feast', { 'attributesToHighlight': ['overview'], 'highlightPreTag': '', 'highlightPostTag': '' }) ``` ```php $client->index('movies')->search('winter feast', [ 'attributesToHighlight' => ['overview'], 'highlightPreTag' => '', 'highlightPostTag' => '' ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder() .q("winter feast") .attributesToHighlight(new String[] {"overview"}) .highlightPreTag("") .highlightPostTag("") .build(); client.index("movies").search(searchRequest); ``` ```ruby client.index('movies').search('winter feast', { attributes_to_highlight: ['overview'], highlight_pre_tag: '', highlight_post_tag: '' }) ``` ```go resp, err := client.Index("movies").Search("winter feast", &meilisearch.SearchRequest{ AttributesToHighlight: []string{"overview"}, HighlightPreTag: "", HighlightPostTag: "", }) ``` ```csharp var sq = new SearchQuery { AttributesToHighlight = new[] {"overview"}, HighlightPreTag = "", HighlightPostTag = "" }; await client.Index("movies").SearchAsync("winter feast", sq); ``` ```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 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 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "winter feast", "showMatchesPosition": true }' ``` ```js client.index('movies').search('winter feast', { showMatchesPosition: true }) ``` ```py client.index('movies').search('winter feast', { 'showMatchesPosition': True }) ``` ```php $client->index('movies')->search('winter feast', [ 'attributesToHighlight' => ['overview'], 'showMatchesPosition' => true ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("winter feast").showMatchesPosition(true).build(); SearchResultPaginated searchResult = client.index("movies").search(searchRequest); ``` ```ruby client.index('movies').search('winter feast', { show_matches_position: true }) ``` ```go resp, err := client.Index("movies").Search("winter feast", &meilisearch.SearchRequest{ ShowMatchesPosition: true, }) ``` ```csharp await client.Index("movies").SearchAsync( "winter feast", new SearchQuery { ShowMatchesPosition = True, }); ``` ```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 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 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 \ -X POST 'MEILISEARCH_URL/indexes/books/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "science fiction", "sort": ["price:asc"] }' ``` ```js client.index('books').search('science fiction', { sort: ['price:asc'], }) ``` ```py client.index('books').search('science fiction', { 'sort': ['price:asc'] }) ``` ```php $client->index('books')->search('science fiction', ['sort' => ['price:asc']]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("science fiction").sort(new String[] {"price:asc"}).build(); client.index("search_parameter_guide_sort_1").search(searchRequest); ``` ```ruby client.index('books').search('science fiction', { sort: ['price:asc'] }) ``` ```go resp, err := client.Index("books").Search("science fiction", &meilisearch.SearchRequest{ Sort: []string{ "price:asc", }, }) ``` ```csharp var sq = new SearchQuery { Sort = new[] { "price:asc" }, }; await client.Index("books").SearchAsync("science fiction", sq); ``` ```rust let results: SearchResults = client .index("books") .search() .with_query("science fiction") .with_sort(&["price:asc"]) .execute() .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/restaurants/search' \ -H 'Content-type:application/json' \ --data-binary '{ "sort": ["_geoPoint(48.8561446,2.2978204):asc"] }' ``` ```js client.index('restaurants').search('', { sort: ['_geoPoint(48.8561446, 2.2978204):asc'], }) ``` ```py client.index('restaurants').search('', { 'sort': ['_geoPoint(48.8561446,2.2978204):asc'] }) ``` ```php $client->index('restaurants')->search('', [ 'sort' => ['_geoPoint(48.8561446,2.2978204):asc'] ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("").sort(new String[] {"_geoPoint(48.8561446,2.2978204):asc"}).build(); client.index("restaurants").search(searchRequest); ``` ```ruby client.index('restaurants').search('', { sort: ['_geoPoint(48.8561446, 2.2978204):asc'] }) ``` ```go resp, err := client.Index("restaurants").Search("", &meilisearch.SearchRequest{ Sort: []string{ "_geoPoint(48.8561446,2.2978204):asc", }, }) ``` ```csharp SearchQuery filters = new SearchQuery() { Sort = new string[] { "_geoPoint(48.8561446,2.2978204):asc" } }; var restaurants = await client.Index("restaurants").SearchAsync("", filters); ``` ```rust let results: SearchResults = client .index("restaurants") .search() .with_sort(&["_geoPoint(48.8561446, 2.2978204):asc"]) .execute() .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "big fat liar", "matchingStrategy": "last" }' ``` ```js client.index('movies').search('big fat liar', { matchingStrategy: 'last' }) ``` ```py client.index('movies').search('big fat liar', { 'matchingStrategy': 'last' }) ``` ```php $client->index('movies')->search('big fat liar', ['matchingStrategy' => 'last']); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("big fat liar").matchingStrategy(MatchingStrategy.LAST).build(); client.index("movies").search(searchRequest); ``` ```ruby client.index('movies').search('big fat liar', { matching_strategy: 'last' }) ``` ```go resp, err := client.Index("movies").Search("big fat liar", &meilisearch.SearchRequest{ MatchingStrategy: Last, }) ``` ```csharp SearchQuery params = new SearchQuery() { MatchingStrategy = "last" }; await client.Index("movies").SearchAsync("big fat liar", params); ``` ```rust let results: SearchResults = client .index("movies") .search() .with_query("big fat liar") .with_matching_strategy(MatchingStrategies::LAST) .execute() .await .unwrap(); ``` ```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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "big fat liar", "matchingStrategy": "all" }' ``` ```js client.index('movies').search('big fat liar', { matchingStrategy: 'all' }) ``` ```py client.index('movies').search('big fat liar', { 'matchingStrategy': 'all' }) ``` ```php $client->index('movies')->search('big fat liar', ['matchingStrategy' => 'all']); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("big fat liar").matchingStrategy(MatchingStrategy.ALL).build(); client.index("movies").search(searchRequest); ``` ```ruby client.index('movies').search('big fat liar', { matching_strategy: 'all' }) ``` ```go resp, err := client.Index("movies").Search("big fat liar", &meilisearch.SearchRequest{ MatchingStrategy: All, }) ``` ```csharp SearchQuery params = new SearchQuery() { MatchingStrategy = "all" }; await client.Index("movies").SearchAsync("big fat liar", params); ``` ```rust let results: SearchResults = client .index("movies") .search() .with_query("big fat liar") .with_matching_strategy(MatchingStrategies::ALL) .execute() .await .unwrap(); ``` ```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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "white shirt", "matchingStrategy": "frequency" }' ``` ```js client.index('movies').search('white shirt', { matchingStrategy: 'frequency' }) ``` ```py client.index('movies').search('big fat liar', { 'matchingStrategy': 'frequency' }) ``` ```php $client->index('movies')->search('white shirt', ['matchingStrategy' => 'frequency']); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("white shirt").matchingStrategy(MatchingStrategy.FREQUENCY).build(); client.index("movies").search(searchRequest); ``` ```ruby client.index('movies').search('white shirt', { matching_strategy: 'frequency' }) ``` ```go client.Index("movies").Search("white shirt", &meilisearch.SearchRequest{ MatchingStrategy: Frequency, }) ``` ```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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "dragon", "showRankingScore": true }' ``` ```js client.index('movies').search('dragon', { showRankingScore: true }) ``` ```py client.index('movies').search('dragon', { 'showRankingScore': True }) ``` ```php $client->index('movies')->search('dragon', [ 'showRankingScore' => true ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("dragon").showRankingScore(true).build(); client.index("movies").search(searchRequest); ``` ```ruby client.index('movies').search('dragon', { show_ranking_score: true }) ``` ```go resp, err := client.Index("movies").Search("dragon", &meilisearch.SearchRequest{ showRankingScore: true, }) ``` ```csharp var params = new SearchQuery() { ShowRankingScore = true }; await client.Index("movies").SearchAsync("dragon", params); ``` ```rust let results: SearchResults = client .index("movies") .search() .with_query("dragon") .with_show_ranking_score(true) .execute() .await .unwrap(); ``` ```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 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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "dragon", "showRankingScoreDetails": true }' ``` ```js client.index('movies').search('dragon', { showRankingScoreDetails: true }) ``` ```py client.index('movies').search('dragon', { 'showRankingScoreDetails': True }) ``` ```php $client->index('movies')->search('dragon', [ 'showRankingScoreDetails' => true ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("dragon").showRankingScoreDetails(true).build(); client.index("movies").search(searchRequest); ``` ```ruby client.index('movies').search('dragon', { show_ranking_score_details: true }) ``` ```go resp, err := client.Index("movies").Search("dragon", &meilisearch.SearchRequest{ showRankingScoreDetails: true, }) ``` ```csharp var params = new SearchQuery() { ShowRankingScoreDetails = true }; await client.Index("movies").SearchAsync("dragon", params); ``` ```rust let results: SearchResults = client .index("movies") .search() .with_query("dragon") .with_show_ranking_score_details(true) .execute() .await .unwrap(); ``` ```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. Excluded results do not count towards `estimatedTotalHits`, `totalHits`, and facet distribution. For performance reasons, if the number of documents above `rankingScoreThreshold` is higher than `limit`, Meilisearch does not evaluate the ranking score of the remaining documents. Results ranking below the threshold are not immediately removed from the set of candidates. In this case, Meilisearch may overestimate the count of `estimatedTotalHits`, `totalHits` and facet distribution. ##### Example The following query only returns results with a ranking score bigger than `0.2`: ```bash curl \ -X POST 'MEILISEARCH_URL/indexes/INDEX_NAME/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "badman", "rankingScoreThreshold": 0.2 }' ``` ```js client.index('INDEX_NAME').search('badman', { rankingScoreThreshold: 0.2 }) ``` ```py client.index('INDEX_NAME').search('badman', { 'rankingScoreThreshold': 0.2 }) ``` ```php $client->index('INDEX_NAME')->search('badman', [ 'rankingScoreThreshold' => 0.2 ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("badman").rankingScoreThreshold(0.2).build(); client.index("INDEX_NAME").search(searchRequest); ``` ```ruby client.index('INDEX_NAME').search('badman', { rankingScoreThreshold: 0.2 }) ``` ```go client.Index("INDEX_NAME").Search("badman", &meilisearch.SearchRequest{ RankingScoreThreshold: 0.2, }) ``` ```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 \ -X POST 'MEILISEARCH_URL/indexes/movies/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "adventure", "attributesToSearchOn": ["overview"] }' ``` ```js client.index('movies').search('adventure', { attributesToSearchOn: ['overview'] }) ``` ```py client.index('movies').search('adventure', { 'attributesToSearchOn': ['overview'] }) ``` ```php $client->index('movies')->search('adventure', [ 'attributesToSearchOn' => ['overview'] ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("adventure").attributesToSearchOn(new String[] {"overview"}); client.index("movies").searchRequest(searchRequest); ``` ```ruby client.index('movies').search('adventure', { attributes_to_search_on: ['overview'] }) ``` ```go resp, err := client.Index("movies").Search("adventure", &meilisearch.SearchRequest{ AttributesToSearchOn: []string{"overview"}, }) ``` ```csharp var searchQuery = new SearchQuery { AttributesToSearchOn = new[] { "overview" } }; await client.Index("movies").SearchAsync("adventure", searchQuery); ``` ```rust let results: SearchResults = client .index("movies") .search() .with_query("adventure") .with_attributes_to_search_on(&["overview"]) .execute() .await .unwrap(); ``` ```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 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 (experimental) **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`. Meilisearch will return an error if you use `hybrid` before activating your instance's `vectorStore` and [configuring an embedder](/reference/api/settings#embedders-experimental). ##### Example ```bash curl -X POST 'localhost:7700/indexes/INDEX_NAME/search' \ -H 'content-type: application/json' \ --data-binary '{ "q": "kitchen utensils", "hybrid": { "semanticRatio": 0.9, "embedder": "EMBEDDER_NAME" } }' ``` ```js client.index('INDEX_NAME').search('kitchen utensils', { hybrid: { semanticRatio: 0.9, embedder: 'EMBEDDER_NAME' } }) ``` ```php $client->index('INDEX_NAME')->search('kitchen utensils', [ 'hybrid' => [ 'semanticRatio' => 0.9, 'embedder' => 'EMBEDDER_NAME' ] ]); ``` #### Vector (experimental) **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 -X POST 'localhost:7700/indexes/INDEX_NAME/search' \ -H 'content-type: application/json' \ --data-binary '{ "vector": [0, 1, 2], "embedder": "EMBEDDER_NAME" }' ``` Meilisearch will return an error if you use `vector` before activating your instance's `vectorStore` and [configuring a custom embedder](/reference/api/settings#embedders-experimental). #### Display `_vectors` in response (experimental) **Parameter**: `retrieveVectors`
**Expected value**: `true` or `false`
**Default value**: `false` Return document embedding data with search results. If `true`, Meilisearch will display vector data in each [document's `_vectors` field](/reference/api/documents#_vectors). ##### Example ```bash curl -X POST 'localhost:7700/indexes/INDEX_NAME/search' \ -H 'content-type: application/json' \ --data-binary '{ "q": "kitchen utensils", "retrieveVectors": true, "hybrid": { "embedder": "EMBEDDER_NAME" } }' ``` ```js client.index('INDEX_NAME').search('kitchen utensils', { retrieveVectors: true, hybrid: { embedder: 'EMBEDDER_NAME' } }) ``` ```php $client->index('INDEX_NAME')->search('kitchen utensils', [ 'retrieveVectors' => true, 'hybrid' => [ 'embedder': 'EMBEDDER_NAME' ] ]); ``` ```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 \ -X POST 'MEILISEARCH_URL/indexes/INDEX_NAME/search' \ -H 'Content-Type: application/json' \ --data-binary '{ "q": "QUERY TEXT IN JAPANESE", "locales": ["jpn"] }' ``` ```js client.index('INDEX_NAME').search('QUERY TEXT IN JAPANESE', { locales: ['jpn'] }) ``` ```py client.index('INDEX_NAME').search('進撃の巨人', { 'locales': ['jpn'] }) ``` ```php $client->index('INDEX_NAME')->search('QUERY TEXT IN JAPANESE', [ 'locales' => ['jpn'] ]); ``` ```java SearchRequest searchRequest = SearchRequest.builder().q("QUERY TEXT IN JAPANESE").locales(new String[]{"jpn"}).build(); client.index("INDEX_NAME").search(searchRequest); ``` ```ruby client.index('INDEX_NAME').search('進撃の巨人', { locales: ['jpn'] }) ``` ```go client.index("INDEX_NAME").Search("QUERY TEXT IN JAPANESE", &meilisearch.SearchRequest{ Locates: []string{"jpn"} }) ``` ```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" } … ], … } ``` --- title: Multi-search — Meilisearch API reference description: The /multi-search route allows you to perform multiple search queries on one or more indexes. --- ## Multi-search 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](/learn/filtering_and_sorting/filter_expression_reference) | `null` | Filter queries by an attribute's value | | **[`facets`](/reference/api/search#facets)** | Array of strings | `null` | Display the count of matches per facet | | **[`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 | | **[`attributesToSearchOn`](/reference/api/search#customize-attributes-to-search-on-at-search-time)** | Array of strings | `["*"]` | Restrict search to the specified attributes | 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` #### Response The response to `/multi-search` queries may take two shapes: federated and non-federated. ##### 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) | 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 | #### Example ##### Non-federated multi-search ```bash 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" } ] }' ``` ```js client.multiSearch({ queries: [ { indexUid: 'movies', q: 'pooh', limit: 5, }, { indexUid: 'movies', q: 'nemo', limit: 5, }, { indexUid: 'movie_ratings', q: 'us', }, ]}) ``` ```py client.multi_search( [ {'indexUid': 'movies', 'q': 'pooh', 'limit': 5}, {'indexUid': 'movies', 'q': 'nemo', 'limit': 5}, {'indexUid': 'movie_ratings', 'q': 'us'} ] ) ``` ```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 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 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 client.MultiSearch(&MultiSearchRequest{ Queries: []SearchRequest{ { IndexUID: "movies", Query: "pooh", Limit: 5, }, { IndexUID: "movies", Query: "nemo", Limit: 5, }, { IndexUID: "movie_ratings", Query: "us", }, }, }) ``` ```csharp 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 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 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 \ -X POST 'MEILISEARCH_URL/multi-search' \ -H 'Content-Type: application/json' \ --data-binary '{ "federation": {}, "queries": [ { "indexUid": "movies", "q": "batman" }, { "indexUid": "comics", "q": "batman" } ] }' ``` ```js client.multiSearch({ federation: {}, queries: [ { indexUid: 'movies', q: 'batman', }, { indexUid: 'comics', q: 'batman', }, ] }) ``` ```py client.multi_search( [{"indexUid": "movies", "q": "batman"}, {"indexUid": "comics", "q": "batman"}], {} ) ``` ```php $client->multiSearch([ (new SearchQuery()) ->setIndexUid('movies')) ->setQuery('batman'), (new SearchQuery()) ->setIndexUid('comics') ->setQuery('batman'), ], (new MultiSearchFederation()) ); ``` ```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 } ``` --- title: Similar documents — Meilisearch API reference description: The /similar route accepts one search result and uses AI-powered search to return a number of similar documents. --- ## Similar documents The `/similar` route uses AI-powered search to return a number of documents similar to a target document. Meilisearch exposes two routes for retrieving similar documents: `POST` and `GET`. In the majority of cases, `POST` will offer better performance and ease of use. This is an experimental feature. To use it, you must first [enable and configure AI-powered search](/learn/ai_powered_search/getting_started_with_ai_search). ### Get similar documents with `POST` Retrieve documents similar to a specific search result. #### Path parameters | Name | Type | Description | | :---------------- | :----- | :--------------------------------------------------------------------- | | **`index_uid`** * | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index | #### Body | Parameter | Type | Default value | Description | | ---------------------------------------------------------------------------- | ---------------- | ------------- | ---------------------------------------------- | | **`id`** | String or number | `null` | Identifier of the target document (mandatory) | | **[`embedder`](/reference/api/search#hybrid-search-experimental)** | String | `"default"` | Embedder to use when computing recommendations. Mandatory | | **[`attributesToRetrieve`](/reference/api/search#attributes-to-retrieve)** | Array of strings | `["*"]` | Attributes to display in the returned documents| | **[`offset`](/reference/api/search#offset)** | Integer | `0` | Number of documents to skip | | **[`limit`](/reference/api/search#limit)** | Integer | `20` | Maximum number of documents returned | | **[`filter`](/reference/api/search#filter)** | String | `null` | Filter queries by an attribute's value | | **[`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` | Display detailed ranking score information | | **[`rankingScoreThreshold`](/reference/api/search#ranking-score-threshold)** | Number | `null` | Exclude results with low ranking scores | | **[`retrieveVectors`](/reference/api/search#display-_vectors-in-response-experimental)** | Boolean | `false` | Return document vector data | #### Example ```bash curl \ -X POST 'MEILISEARCH_URL/indexes/INDEX_NAME/similar' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer DEFAULT_SEARCH_API_KEY' \ --data-binary '{ "id": TARGET_DOCUMENT_ID, "embedder": "EMBEDDER_NAME" }' ``` ```js client.index('INDEX_NAME').searchSimilarDocuments({ id: 'TARGET_DOCUMENT_ID', embedder: 'default' }) ``` ```py client.index("INDEX_NAME").get_similar_documents({"id": "TARGET_DOCUMENT_ID", "embedder": "default"}) ``` ```php $similarQuery = new SimilarDocumentsQuery('TARGET_DOCUMENT_ID', 'default'); $client->index('INDEX_NAME')->searchSimilarDocuments($similarQuery); ``` ```java SimilarDocumentRequest query = new SimilarDocumentRequest() .setId("143") .setEmbedder("manual"); client.index("movies").searchSimilarDocuments(query) ``` ```ruby client.index('INDEX_NAME').search_similar_documents('TARGET_DOCUMENT_ID', embedder: 'default') ``` ```go resp := new(meilisearch.SimilarDocumentResult) client.Index("INDEX_NAME").SearchSimilarDocuments(&meilisearch.SimilarDocumentQuery{ Id: "TARGET_DOCUMENT_ID", Embedder: "default", }, resp) ``` ##### Response: `200 OK` ```json { "hits": [ { "id": "299537", "title": "Captain Marvel" }, { "id": "166428", "title": "How to Train Your Dragon: The Hidden World" } { "id": "287947", "title": "Shazam!" } ], "id": "23", "processingTimeMs": 0, "limit": 20, "offset": 0, "estimatedTotalHits": 3 } ``` ### Get similar documents with `GET` Retrieve documents similar to a specific search result. #### Path parameters | Name | Type | Description | | :---------------- | :----- | :--------------------------------------------------------------------- | | **`index_uid`** * | String | [`uid`](/learn/getting_started/indexes#index-uid) of the requested index | #### Query parameters | Parameter | Type | Default value | Description | | ---------------------------------------------------------------------------- | ---------------- | ------------- | ---------------------------------------------- | | **`id`** | String or number | `null` | Identifier of the target document (mandatory) | | **[`embedder`](/reference/api/search#hybrid-search-experimental)** | String | `"default"` | Embedder to use when computing recommendations. Mandatory | | **[`attributesToRetrieve`](/reference/api/search#attributes-to-retrieve)** | Array of strings | `["*"]` | Attributes to display in the returned documents| | **[`offset`](/reference/api/search#offset)** | Integer | `0` | Number of documents to skip | | **[`limit`](/reference/api/search#limit)** | Integer | `20` | Maximum number of documents returned | | **[`filter`](/reference/api/search#filter)** | String | `null` | Filter queries by an attribute's value | | **[`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` | Display detailed ranking score information | | **[`rankingScoreThreshold`](/reference/api/search#ranking-score-threshold)** | Number | `null` | Exclude results with low ranking scores | | **[`retrieveVectors`](/reference/api/search#display-_vectors-in-response-experimental)** | Boolean | `false` | Return document vector data | #### Example ```bash curl \ -X GET 'MEILISEARCH_URL/indexes/INDEX_NAME/similar?id=TARGET_DOCUMENT_ID&embedder=EMBEDDER_NAME' ``` ##### Response: `200 OK` ```json { "hits": [ { "id": "299537", "title": "Captain Marvel" }, { "id": "166428", "title": "How to Train Your Dragon: The Hidden World" } { "id": "287947", "title": "Shazam!" } ], "id": "23", "processingTimeMs": 0, "limit": 20, "offset": 0, "estimatedTotalHits": 3 } ``` --- title: Facet search — Meilisearch API reference description: The /facet-search route allows you to search for facet values. --- ## Facet search 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. You can configure how facets are sorted using the [`sortFacetValuesBy`](/reference/api/settings#faceting-object) property of the `faceting` index settings. 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 performs a [placeholder search](/reference/api/search#placeholder-search) which 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 | #### 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 \ -X POST 'MEILISEARCH_URL/indexes/books/facet-search' \ -H 'Content-Type: application/json' \ --data-binary '{ "facetQuery": "fiction", "facetName": "genres", "filter": "rating > 3" }' ``` ```js client.index('books').searchForFacetValues({ facetQuery: 'fiction', facetName: 'genres' filter: 'rating > 3' }) ``` ```py client.index('books').facet_search('genres', 'fiction', { 'filter': 'rating > 3' }) ``` ```php $client->index('books')->facetSearch( (new FacetSearchQuery()) ->setFacetQuery('fiction') ->setFacetName('genres') ->setFilter(['rating > 3']) ); ``` ```java FacetSearchRequest fsr = FacetSearchRequest.builder().facetName("genres").facetQuery("fiction").filter(new String[]{"rating > 3"}).build(); client.index("books").facetSearch(fsr); ``` ```ruby client.index('books').facet_search('genres', 'fiction', filter: 'rating > 3') ``` ```go client.Index("books").FacetSearch(&meilisearch.FacetSearchRequest{ FacetQuery: "fiction", FacetName: "genres", Filter: "rating > 3", }) ``` ```csharp var query = new SearchFacetsQuery() { FacetQuery = "fiction", Filter = "rating > 3" }; await client.Index("books").FacetSearchAsync("genres", query); ``` ```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 } ``` --- title: Tasks — Meilisearch API reference description: The /tasks route allows you to manage and monitor Meilisearch's asynchronous operations. --- ## Tasks The `/tasks` route gives information about the progress of [asynchronous operations](/learn/async/asynchronous_operations). ### Task object ```json { "uid": 4, "batchUids": 0, "indexUid": "movie", "status": "failed", "type": "indexDeletion", "canceledBy": null, "details": { "deletedDocuments": 0 }, "error": { "message": "Index `movie` not found.", "code": "index_not_found", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#index_not_found" }, "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` **Type**: Integer
**Description**: Unique sequential identifier of the task. The task `uid` is incremented across all indexes in an instance. #### `batchUid` **Type**: Integer
**Description**: Unique sequential identifier of the batch this task belongs to. The batch `uid` is incremented across all indexes in an instance. #### `indexUid` **Type**: String
**Description**: Unique identifier of the targeted index This value is always `null` for [global tasks](/learn/async/asynchronous_operations#global-tasks). #### `status` **Type**: String
**Description**: Status of the task. Possible values are `enqueued`, `processing`, `succeeded`, `failed`, and `canceled` #### `type` **Type**: String
**Description**: Type of operation performed by the task. Possible values are `indexCreation`, `indexUpdate`, `indexDeletion`, `indexSwap`, `documentAdditionOrUpdate`, `documentDeletion`, `settingsUpdate`, `dumpCreation`, `taskCancelation`, `taskDeletion`, and `snapshotCreation` #### `canceledBy` **Type**: Integer
**Description**: If the task was canceled, `canceledBy` contains the `uid` of a `taskCancelation` task. If the task was not canceled, `canceledBy` is always `null` #### `details` **Type**: Object
**Description**: Detailed information on the task payload. This object's contents depend on the task's `type` ##### `documentAdditionOrUpdate` | Name | Description | | :---------------------- | :-------------------------------------------------------------------------------------- | | **`receivedDocuments`** | Number of documents received | | **`indexedDocuments`** | Number of documents indexed. `null` while the task status is `enqueued` or `processing` | ##### `documentDeletion` | Name | Description | | :--------------------- | :-------------------------------------------------------------------------------------- | | **`providedIds`** | Number of documents queued for deletion | | **`originalFilter`** | The filter used to delete documents. `null` if it was not specified | | **`deletedDocuments`** | Number of documents deleted. `null` while the task status is `enqueued` or `processing` | ##### `indexCreation` | Name | Description | | :--------------- | :--------------------------------------------------------------------------------------------- | | **`primaryKey`** | Value of the `primaryKey` field supplied during index creation. `null` if it was not specified | ##### `indexUpdate` | Name | Description | | :--------------- | :------------------------------------------------------------------------------------------- | | **`primaryKey`** | Value of the `primaryKey` field supplied during index update. `null` if it was not specified | ##### `indexDeletion` | Name | Description | | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **`deletedDocuments`** | Number of deleted documents. This should equal the total number of documents in the deleted index. `null` while the task status is `enqueued` or `processing` | ##### `indexSwap` | Name | Description | | :---------- | :----------------------------------------------------- | | **`swaps`** | Object containing the payload for the `indexSwap` task | ##### `settingsUpdate` | Name | Description | | :------------------------- | :---------------------------- | | **`rankingRules`** | List of ranking rules | | **`filterableAttributes`** | List of filterable attributes | | **`distinctAttribute`** | The distinct attribute | | **`searchableAttributes`** | List of searchable attributes | | **`displayedAttributes`** | List of displayed attributes | | **`sortableAttributes`** | List of sortable attributes | | **`stopWords`** | List of stop words | | **`synonyms`** | List of synonyms | | **`typoTolerance`** | The `typoTolerance` object | | **`pagination`** | The `pagination` object | | **`faceting`** | The `faceting` object | ##### `dumpCreation` | Name | Description | | :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **`dumpUid`** | The generated `uid` of the dump. This is also the name of the generated dump file. `null` when the task status is `enqueued`, `processing`, `canceled`, or `failed` | ##### `taskCancelation` | Name | Description | | :------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`matchedTasks`** | The number of matched tasks. If the API key used for the request doesn’t have access to an index, tasks relating to that index will not be included in `matchedTasks` | | **`canceledTasks`** | The number of tasks successfully canceled. If the task cancellation fails, this will be `0`. `null` when the task status is `enqueued` or `processing` | | **`originalFilter`** | The filter used in the [cancel task](#cancel-tasks) request | Task cancellation can be successful and still have `canceledTasks: 0`. This happens when `matchedTasks` matches finished tasks (`succeeded`, `failed`, or `canceled`). ##### `taskDeletion` | Name | Description | | :------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`matchedTasks`** | The number of matched tasks. If the API key used for the request doesn’t have access to an index, tasks relating to that index will not be included in `matchedTasks` | | **`deletedTasks`** | The number of tasks successfully deleted. If the task deletion fails, this will be `0`. `null` when the task status is `enqueued` or `processing` | | **`originalFilter`** | The filter used in the [delete task](#delete-tasks) request | Task deletion can be successful and still have `deletedTasks: 0`. This happens when `matchedTasks` matches `enqueued` or `processing` tasks. ##### `snapshotCreation` The `details` object is set to `null` for `snapshotCreation` tasks. #### `error` **Type**: Object
**Description**: If the task has the `failed` [status](#status), then this object contains the error definition. Otherwise, set to `null` | Name | Description | | :------------ | :-------------------------------------------------- | | **`message`** | A human-readable description of the error | | **`code`** | The [error code](/reference/errors/error_codes) | | **`type`** | The [error type](/reference/errors/overview#errors) | | **`link`** | A link to the relevant section of the documentation | #### `duration` **Type**: String
**Description**: The total elapsed time the task spent in the `processing` state, in [ISO 8601](https://www.ionos.com/digitalguide/websites/web-development/iso-8601/) format #### `enqueuedAt` **Type**: String
**Description**: The date and time when the task was first `enqueued`, in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format #### `startedAt` **Type**: String
**Description**: The date and time when the task began `processing`, in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format #### `finishedAt` **Type**: String
**Description**: The date and time when the task finished `processing`, whether `failed`, `succeeded`, or `canceled`, in [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format #### Summarized task object When an API request triggers an asynchronous process, Meilisearch returns a summarized task object. This object contains the following fields: | Field | Type | Description | | :--------------- | :------ | :---------------------------------------------------------------------------------------------------------------------------- | | **`taskUid`** | Integer | Unique sequential identifier | | **`indexUid`** | String | Unique index identifier (always `null` for [global tasks](/learn/async/asynchronous_operations#global-tasks)) | | **`status`** | String | Status of the task. Value is `enqueued` | | **`type`** | String | Type of task | | **`enqueuedAt`** | String | Represents the date and time in the [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format when the task has been `enqueued` | You can use this `taskUid` to get more details on [the status of the task](#get-one-task). ### Get tasks List all tasks globally, regardless of index. The `task` objects are contained in the `results` array. Tasks are always returned in descending order of `uid`. This means that by default, **the most recently created `task` objects appear first**. Task results are [paginated](/learn/async/paginating_tasks) and can be [filtered](/learn/async/filtering_tasks). #### Query parameters | Query Parameter | Default Value | Description | | :--------------------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`uids`** | `*` (all uids) | [Filter tasks](/learn/async/filtering_tasks) by their `uid`. Separate multiple task `uids` with a comma (`,`) | | **`batchUids`** | `*` (all batch uids) | [Filter tasks](/learn/async/filtering_tasks) by their `batchUid`. Separate multiple `batchUids` with a comma (`,`) | | **`statuses`** | `*` (all statuses) | [Filter tasks](/learn/async/filtering_tasks) by their `status`. Separate multiple task `statuses` with a comma (`,`) | | **`types`** | `*` (all types) | [Filter tasks](/learn/async/filtering_tasks) by their `type`. Separate multiple task `types` with a comma (`,`) | | **`indexUids`** | `*` (all indexes) | [Filter tasks](/learn/async/filtering_tasks) by their `indexUid`. Separate multiple task `indexUids` with a comma (`,`). Case-sensitive | | **`limit`** | `20` | Number of tasks to return | | **`from`** | `uid` of the last created task | `uid` of the first task returned | | **`reverse`** | `false` | If `true`, returns results in the reverse order, from oldest to most recent | | **`canceledBy`** | N/A | [Filter tasks](/learn/async/filtering_tasks) by their `canceledBy` field. Separate multiple task `uids` with a comma (`,`) | | **`beforeEnqueuedAt`** | `*` (all tasks) | [Filter tasks](/learn/async/filtering_tasks) by their `enqueuedAt` field | | **`beforeStartedAt`** | `*` (all tasks) | [Filter tasks](/learn/async/filtering_tasks) by their `startedAt` field | | **`beforeFinishedAt`** | `*` (all tasks) | [Filter tasks](/learn/async/filtering_tasks) by their `finishedAt` field | | **`afterEnqueuedAt`** | `*` (all tasks) | [Filter tasks](/learn/async/filtering_tasks) by their `enqueuedAt` field | | **`afterStartedAt`** | `*` (all tasks) | [Filter tasks](/learn/async/filtering_tasks) by their `startedAt` field | | **`afterFinishedAt`** | `*` (all tasks) | [Filter tasks](/learn/async/filtering_tasks) by their `finishedAt` field | #### Response | Name | Type | Description | | :------------ | :------ | :----------------------------------------------------------------------------------------------------------------------------- | | **`results`** | Array | An array of [task objects](#task-object) | | **`total`** | Integer | Total number of tasks matching the filter or query | | **`limit`** | Integer | Number of tasks returned | | **`from`** | Integer | `uid` of the first task 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 \ -X GET 'MEILISEARCH_URL/tasks' ``` ```js client.getTasks() ``` ```py client.get_tasks() ``` ```php $client->getTasks(); ``` ```java client.getTasks(); ``` ```ruby client.tasks ``` ```go client.GetTasks(nil); ``` ```csharp ResourceResults taskResult = await client.GetTasksAsync(); ``` ```rust let tasks: TasksResults = client .get_tasks() .await .unwrap(); ``` ```swift client.getTasks { (result) in switch result { case .success(let tasks): print(tasks) case .failure(let error): print(error) } } ``` ```dart await client.getTasks(); ``` ##### Response: `200 Ok` ```json { "results": [ { "uid": 1, "indexUid": "movies_reviews", "status": "failed", "type": "documentAdditionOrUpdate", "canceledBy": null, "details": { "receivedDocuments": 100, "indexedDocuments": 0 }, "error": null, "duration": null, "enqueuedAt": "2021-08-12T10:00:00.000000Z", "startedAt": null, "finishedAt": null }, { "uid": 0, "indexUid": "movies", "status": "succeeded", "type": "documentAdditionOrUpdate", "canceledBy": null, "details": { "receivedDocuments": 100, "indexedDocuments": 100 }, "error": null, "duration": "PT16S", "enqueuedAt": "2021-08-11T09:25:53.000000Z", "startedAt": "2021-08-11T10:03:00.000000Z", "finishedAt": "2021-08-11T10:03:16.000000Z" } ], "total": 50, "limit": 20, "from": 1, "next": null } ``` ### Get one task Get a single task. If you try retrieving a deleted task, Meilisearch will return a [`task_not_found`](/reference/errors/error_codes#task_not_found) error. #### Path parameters | Name | Type | Description | | :--------------- | :----- | :---------------------------------- | | **`task_uid`** * | String | [`uid`](#uid) of the requested task | #### Example ```bash curl \ -X GET 'MEILISEARCH_URL/tasks/1' ``` ```js client.getTask(1) ``` ```py client.get_task(1) ``` ```php $client->getTask(1); ``` ```java client.getTask(1); ``` ```ruby client.task(1) ``` ```go client.GetTask(1); ``` ```csharp TaskInfo task = await client.GetTaskAsync(1); ``` ```rust let task: Task = client .get_task(1) .await .unwrap(); ``` ```swift client.getTask(taskUid: 1) { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```dart await client.getTask(1); ``` ##### Response: `200 Ok` ```json { "uid": 1, "indexUid": "movies", "status": "succeeded", "type": "settingsUpdate", "canceledBy": null, "details": { "rankingRules": [ "typo", "ranking:desc", "words", "proximity", "attribute", "exactness" ] }, "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" } ``` ### Cancel tasks Cancel any number of `enqueued` or `processing` tasks based on their `uid`, `status`, `type`, `indexUid`, or the date at which they were enqueued (`enqueuedAt`) or processed (`startedAt`). Task cancellation is an atomic transaction: **either all tasks are successfully canceled or none are**. To prevent users from accidentally canceling all enqueued and processing tasks, Meilisearch throws the [`missing_task_filters`](/reference/errors/error_codes#missing_task_filters) error if this route is used without any filters (`POST /tasks/cancel`). You can also cancel `taskCancelation` type tasks as long as they are in the `enqueued` or `processing` state. This is possible because `taskCancelation` type tasks are processed in reverse order, such that the last one you enqueue will be processed first. #### Query parameters A valid `uids`, `statuses`, `types`, `indexUids`, or date(`beforeXAt` or `afterXAt`) parameter is required. | Query Parameter | Description | | :--------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | | **`uids`** | Cancel tasks based on `uid`. Separate multiple `uids` with a comma (`,`). Use `uids=*` for all `uids` | | **`statuses`** | Cancel tasks based on `status`. Separate multiple `statuses` with a comma (`,`). Use `statuses=*` for all `statuses` | | **`types`** | Cancel tasks based on `type`. Separate multiple `types` with a comma (`,`). Use `types=*` for all `types` | | **`indexUids`** | Cancel tasks based on `indexUid`. Separate multiple `uids` with a comma (`,`). Use `indexUids=*` for all `indexUids`. Case-sensitive | | **`beforeEnqueuedAt`** | Cancel tasks **before** a specified `enqueuedAt` date. Use `beforeEnqueuedAt=*` to cancel all tasks | | **`beforeStartedAt`** | Cancel tasks **before** a specified `startedAt` date. Use `beforeStartedAt=*` to cancel all tasks | | **`afterEnqueuedAt`** | Cancel tasks **after** a specified `enqueuedAt` date. Use `afterEnqueuedAt=*` to cancel all tasks | | **`afterStartedAt`** | Cancel tasks **after** a specified `startedAt` date. Use `afterStartedAt=*` to cancel all tasks | Date filters are equivalent to `<` or `>` operations. At this time, there is no way to perform a `≤` or `≥` operations with a date filter. [To learn more about filtering tasks, refer to our dedicated guide.](/learn/async/filtering_tasks) #### Example ```bash curl \ -X POST 'MEILISEARCH_URL/tasks/cancel?uids=1,2' ``` ```js client.cancelTasks({ uids: [1, 2] }) ``` ```py client.cancel_tasks({'uids': ['1', '2']}) ``` ```php $client->cancelTasks((new CancelTasksQuery())->setUids([1, 2])); ``` ```java CancelTasksQuery query = new CancelTasksQuery().setUids(new int[] {1, 2}) client.cancelTasks(query); ``` ```ruby client.cancel_tasks(uids: [1, 2]) ``` ```go client.CancelTasks(&meilisearch.CancelTasksQuery{ UIDS: []int64{1, 2}, }); ``` ```csharp await client.CancelTasksAsync(new CancelTasksQuery { Uids = new List { 1, 2 } }); ``` ```rust let mut query = tasks::TasksCancelQuery::new(&client); query.with_uids([1, 2]); let res = client.cancel_task_with(&query).await.unwrap(); ``` ```swift client.cancelTasks(filter: CancelTasksQuery(uids: [1, 2])) { (result) in switch result { case .success(let taskInfo): print(taskInfo) case .failure(let error): print(error) } } ``` ```dart await client.cancelTasks(params: CancelTasksQuery(uids: [1, 2])); ``` ##### Response: `200 Ok` ```json { "taskUid": 3, "indexUid": null, "status": "enqueued", "type": "taskCancelation", "enqueuedAt": "2021-08-12T10:00:00.000000Z" } ``` Since `taskCancelation` is a [global task](/learn/async/asynchronous_operations#global-tasks), its `indexUid` is always `null`. You can use this `taskUid` to get more details on the [status of the task](#get-one-task). #### Cancel all tasks You can cancel all `processing` and `enqueued` tasks using the following filter: The API key used must have access to all indexes (`"indexes": [*]`) and the [`task.cancel`](/reference/api/keys#actions) action. ### Delete tasks Delete a finished (`succeeded`, `failed`, or `canceled`) task based on `uid`, `status`, `type`, `indexUid`, `canceledBy`, or date. Task deletion is an atomic transaction: **either all tasks are successfully deleted, or none are**. To prevent users from accidentally deleting the entire task history, Meilisearch throws the [`missing_task_filters`](/reference/errors/error_codes#missing_task_filters) error if this route is used without any filters (DELETE `/tasks`). #### Query parameters A valid `uids`, `statuses`, `types`, `indexUids`, `canceledBy`, or date(`beforeXAt` or `afterXAt`) parameter is required. | Query Parameter | Description | | :--------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | | **`uids`** | Delete tasks based on `uid`. Separate multiple `uids` with a comma (`,`). Use `uids=*` for all `uids` | | **`statuses`** | Delete tasks based on `status`. Separate multiple `statuses` with a comma (`,`). Use `statuses=*` for all `statuses` | | **`types`** | Delete tasks based on `type`. Separate multiple `types` with a comma (`,`). Use `types=*` for all `types` | | **`indexUids`** | Delete tasks based on `indexUid`. Separate multiple `uids` with a comma (`,`). Use `indexUids=*` for all `indexUids`. Case-sensitive | | **`canceledBy`** | Delete tasks based on the `canceledBy` field | | **`beforeEnqueuedAt`** | Delete tasks **before** a specified `enqueuedAt` date. Use `beforeEnqueuedAt=*` to delete all tasks | | **`beforeStartedAt`** | Delete tasks **before** a specified `startedAt` date. Use `beforeStartedAt=*` to delete all tasks | | **`beforeFinishedAt`** | Delete tasks **before** a specified `finishedAt` date. Use `beforeFinishedAt=*` to delete all tasks | | **`afterEnqueuedAt`** | Delete tasks **after** a specified `enqueuedAt` date. Use `afterEnqueuedAt=*` to delete all tasks | | **`afterStartedAt`** | Delete tasks **after** a specified `startedAt` date. Use `afterStartedAt=*` to delete all tasks | | **`afterFinishedAt`** | Delete tasks **after** a specified `finishedAt` date. Use `afterFinishedAt=*` to delete all tasks | Date filters are equivalent to `<` or `>` operations. At this time, there is no way to perform a `≤` or `≥` operations with a date filter. [To learn more about filtering tasks, refer to our dedicated guide.](/learn/async/filtering_tasks) #### Example ```bash curl \ -X DELETE 'MEILISEARCH_URL/tasks?uids=1,2' ``` ```js client.deleteTasks({ uids: [1, 2] }) ``` ```py client.delete_tasks({'uids': ['1', '2']}) ``` ```php $client->deleteTasks((new DeleteTasksQuery())->setUids([1, 2])); ``` ```java DeleteTasksQuery query = new DeleteTasksQuery().setUids(new int[] {1, 2}) client.deleteTasks(query); ``` ```ruby client.delete_tasks(uids: [1, 2]) ``` ```go client.DeleteTaks(&meilisearch.DeleteTasksQuery{ UIDS: []int64{1, 2}, }); ``` ```csharp await client.DeleteTasksAsync(new DeleteTasksQuery { Uids = new List { 1, 2 } }); ``` ```rust let mut query = tasks::TasksDeleteQuery::new(&client); query.with_uids([1, 2]); let res = client.delete_tasks_with(&query).await.unwrap(); ``` ```swift client.deleteTasks(filter: DeleteTasksQuery(uids: [1, 2])) { (result) in switch result { case .success(let taskInfo): print(taskInfo) case .failure(let error): print(error) } } ``` ```dart await client.deleteTasks(params: DeleteTasksQuery(uids: [1, 2])); ``` ##### Response: `200 Ok` ```json { "taskUid": 3, "indexUid": null, "status": "enqueued", "type": "taskDeletion", "enqueuedAt": "2021-08-12T10:00:00.000000Z" } ``` Since `taskDeletion` is a [global task](/learn/async/asynchronous_operations#global-tasks), its `indexUid` is always `null`. You can use this `taskUid` to get more details on the [status of the task](#get-one-task). #### Delete all tasks You can delete all finished tasks by using the following filter: The API key used must have access to all indexes (`"indexes": [*]`) and the [`task.delete`](/reference/api/keys#actions) action. --- title: Batches — Meilisearch API reference description: The /batches route allows you to monitor how Meilisearch is grouping and processing asynchronous operations. --- ## Batches The `/batches` route gives information about the progress of batches of [asynchronous operations](/learn/async/asynchronous_operations). ### Batch object ```json { "uid": 0, "details": { "receivedDocuments": 6, "indexedDocuments": 6 }, "stats": { "totalNbTasks": 1, "status": { "succeeded": 1 }, "types": { "documentAdditionOrUpdate": 1 }, "indexUids": { "INDEX_NAME": 1 } }, "duration": "PT0.250518S", "startedAt": "2024-12-10T15:20:30.18182Z", "finishedAt": "2024-12-10T15:20:30.432338Z", "progress": { "steps": [ { "currentStep": "extracting words", "finished": 2, "total": 9, }, { "currentStep": "document", "finished": 30546, "total": 31944, } ], "percentage": 32.8471 } } ``` #### `uid` **Type**: Integer
**Description**: Unique sequential identifier of the batch. Starts at `0` and increases by one for every new patch. #### `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. #### `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 ### 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 \ -X GET 'http://localhost:7700/batches' ``` ```js client.getBatches(); ``` ```py client.get_batches() ``` ```php $client->getBatches(); ``` ```ruby client.batches ``` ##### Response: `200 Ok` ```json { "results": [ { "uid": 2, "details": { "stopWords": [ "of", "the" ] }, "progress": null, "stats": { "totalNbTasks": 1, "status": { "succeeded": 1 }, "types": { "settingsUpdate": 1 }, "indexUids": { "INDEX_NAME": 1 } }, "duration": "PT0.110083S", "startedAt": "2024-12-10T15:49:04.995321Z", "finishedAt": "2024-12-10T15:49:05.105404Z" } ], "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 \ -X GET 'http://localhost:7700/batches/BATCH_UID' ``` ```js client.getBatch(BATCH_UID); ``` ```py client.get_batch(BATCH_UID) ``` ```php $client->getBatch(BATCH_UID); ``` ```ruby client.batch(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 } }, "duration": "PT0.364788S", "startedAt": "2024-12-10T15:48:49.672141Z", "finishedAt": "2024-12-10T15:48:50.036929Z" } ``` --- title: Keys — Meilisearch API reference description: The /keys route allows you to create, manage, and delete API keys. --- ## Keys 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` 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 | #### `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 \ -X GET 'MEILISEARCH_URL/keys?limit=3' \ -H 'Authorization: Bearer MASTER_KEY' ``` ```js client.getKeys({ limit: 3 }) ``` ```py client.get_keys({'limit': 3}) ``` ```php $client->getKeys((new KeysQuery())->setLimit(3)); ``` ```java KeysQuery query = new KeysQuery().setLimit(3); client.getKeys(query); ``` ```ruby client.keys(limit: 3) ``` ```go client.GetKeys(&meilisearch.KeysQuery{ Limit: 3 }); ``` ```csharp ResourceResults keyResult = await client.GetKeysAsync(new KeysQuery { Limit = 3 }); ``` ```rust let mut query = KeysQuery::new() .with_limit(3) .execute(&client) .await .unwrap(); ``` ```swift client.getKeys(params: KeysQuery(limit: 3)) { result in switch result { case .success(let keys): print(keys) case .failure(let error): print(error) } } ``` ```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 \ -X GET 'MEILISEARCH_URL/keys/6062abda-a5aa-4414-ac91-ecd7944c0f8d' \ -H 'Authorization: Bearer MASTER_KEY' ``` ```js client.getKey('6062abda-a5aa-4414-ac91-ecd7944c0f8d') ``` ```py client.get_key('6062abda-a5aa-4414-ac91-ecd7944c0f8d') ``` ```php $client->getKey('6062abda-a5aa-4414-ac91-ecd7944c0f8d'); ``` ```java client.getKey("6062abda-a5aa-4414-ac91-ecd7944c0f8d"); ``` ```ruby client.key('6062abda-a5aa-4414-ac91-ecd7944c0f8d') ``` ```go client.GetKey("6062abda-a5aa-4414-ac91-ecd7944c0f8d") ``` ```csharp Key key = await client.GetKeyAsync("d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4"); ``` ```rust let key = client .get_key("6062abda-a5aa-4414-ac91-ecd7944c0f8d") .await .unwrap(); ``` ```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 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 \ -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" }' ``` ```js client.createKey({ description: 'Add documents: Products API key', actions: ['documents.add'], indexes: ['products'], expiresAt: '2021-11-13T00:00:00Z' }) ``` ```py client.create_key(options={ 'description': 'Add documents: Products API key', 'actions': ['documents.add'], 'indexes': ['products'], 'expiresAt': '2042-04-02T00:42:42Z' }) ``` ```php $client->createKey([ 'description' => 'Add documents: Products API key', 'actions' => ['documents.add'], 'indexes' => ['products'], 'expiresAt' => '2042-04-02T00:42:42Z', ]); ``` ```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 client.create_key( description: 'Add documents: Products API key', actions: ['documents.add'], indexes: ['products'], expires_at: '2042-04-02T00:42:42Z' ) ``` ```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 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 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 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 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 \ -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" }' ``` ```js client.updateKey('6062abda-a5aa-4414-ac91-ecd7944c0f8d', { name: 'Products/Reviews API key', description: 'Manage documents: Products/Reviews API key', }) ``` ```py 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 $client->updateKey('6062abda-a5aa-4414-ac91-ecd7944c0f8d', [ 'name' => 'Products/Reviews API key', 'description' => 'Manage documents: Products/Reviews API key' ]); ``` ```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 client.update_key( '6062abda-a5aa-4414-ac91-ecd7944c0f8d', { description: 'Manage documents: Products/Reviews API key', name: 'Products/Reviews API key' } ) ``` ```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 await client.UpdateKeyAsync( "6062abda-a5aa-4414-ac91-ecd7944c0f8d", description: "Manage documents: Products/Reviews API key", name: "Products/Reviews API key" ) ``` ```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 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 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 \ -X DELETE 'MEILISEARCH_URL/keys/6062abda-a5aa-4414-ac91-ecd7944c0f8d' \ -H 'Authorization: Bearer MASTER_KEY' ``` ```js client.deleteKey('6062abda-a5aa-4414-ac91-ecd7944c0f8d') ``` ```py client.delete_key('6062abda-a5aa-4414-ac91-ecd7944c0f8d') ``` ```php $client->deleteKey('6062abda-a5aa-4414-ac91-ecd7944c0f8d'); ``` ```java client.deleteKey("6062abda-a5aa-4414-ac91-ecd7944c0f8d") ``` ```ruby client.delete_key('6062abda-a5aa-4414-ac91-ecd7944c0f8d') ``` ```go client.DeleteKey("6062abda-a5aa-4414-ac91-ecd7944c0f8d") ``` ```csharp client.DeleteKeyAsync("6062abda-a5aa-4414-ac91-ecd7944c0f8d") ``` ```rust let key = client .get_key("6062abda-a5aa-4414-ac91-ecd7944c0f8d") .await .unwrap(); client .delete_key(&key) .await?; ``` ```swift client.deleteKey(keyOrUid: "6062abda-a5aa-4414-ac91-ecd7944c0f8d") { result in switch result { case .success: print("success") case .failure(let error): print(error) } } ``` ```dart await client.deleteKey('6062abda-a5aa-4414-ac91-ecd7944c0f8d'); ``` ##### Response: `204 No Content` --- title: Settings — Meilisearch API reference description: The /settings route allows you to customize search settings for the given index. sidebarDepth: 3 --- ## Settings 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 } ``` ### 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 \ -X GET 'MEILISEARCH_URL/indexes/movies/settings' ``` ```js client.index('movies').getSettings() ``` ```py client.index('movies').get_settings() ``` ```php $client->index('movies')->getSettings(); ``` ```java client.index("movies").getSettings(); ``` ```ruby client.index('movies').settings ``` ```go client.Index("movies").GetSettings() ``` ```csharp await client.Index("movies").GetSettingsAsync(); ``` ```rust let settings: Settings = client .index("movies") .get_settings() .await .unwrap(); ``` ```swift client.index("movies").getSettings { (result) in switch result { case .success(let setting): print(setting) case .failure(let error): print(error) } } ``` ```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 } ``` #### 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 | 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-experimental)** | Object of objects | [Default object](#embedders-object) | Embedder required for performing meaning-based search queries | ##### Example ```bash 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 }' ``` ```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 }) ``` ```py 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 $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 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 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 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 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 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 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 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 \ -X DELETE 'MEILISEARCH_URL/indexes/movies/settings' ``` ```js client.index('movies').resetSettings() ``` ```py client.index('movies').reset_settings() ``` ```php $client->index('movies')->resetSettings(); ``` ```java client.index("movies").resetSettings(); ``` ```ruby client.index('movies').reset_settings ``` ```go client.Index("movies").ResetSettings() ``` ```csharp await client.Index("movies").ResetSettingsAsync(); ``` ```rust let task: TaskInfo = client .index("movies") .reset_settings() .await .unwrap(); ``` ```swift client.index("movies").resetSettings { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X GET 'MEILISEARCH_URL/indexes/books/settings/dictionary' ``` ```js client.index('books').getDictionary() ``` ```py client.index('books').get_dictionary() ``` ```php $client->index('books')->getDictionary(); ``` ```java client.index("books").getDictionarySettings(); ``` ```ruby client.index('books').dictionary ``` ```go client.Index("books").GetDictionary() ``` ```csharp var indexDictionary = await client.Index("books").GetDictionaryAsync(); ``` ```rust let task: TaskInfo = client .index('books') .get_dictionary() .await .unwrap(); ``` ```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 \ -X PUT 'MEILISEARCH_URL/indexes/books/settings/dictionary' \ -H 'Content-Type: application/json' \ --data-binary '[ "J. R. R.", "W. E. B." ]' ``` ```js client.index('books').updateDictionary(['J. R. R.', 'W. E. B.']) ``` ```py client.index('books').update_dictionary(["J. R. R.", "W. E. B."]) ``` ```php $client->index('books')->updateDictionary(['J. R. R.', 'W. E. B.']); ``` ```java client.index("books").updateDictionarySettings(new String[] {"J. R. R.", "W. E. B."}); ``` ```ruby client.index('books').update_dictionary(['J. R. R.', 'W. E. B.']) ``` ```go client.Index("books").UpdateDictionary([]string{ "J. R. R.", "W. E. B.", }) ``` ```csharp var newDictionary = new string[] { "J. R. R.", "W. E. B." }; await client.Index("books").UpdateDictionaryAsync(newDictionary); ``` ```rust let task: TaskInfo = client .index('books') .set_dictionary(['J. R. R.', 'W. E. B.']) .await .unwrap(); ``` ```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 \ -X DELETE 'MEILISEARCH_URL/indexes/books/settings/dictionary' ``` ```js client.index('books').resetDictionary() ``` ```py client.index('books').reset_dictionary() ``` ```php $client->index('books')->resetDictionary(); ``` ```java client.index("books").resetDictionarySettings(); ``` ```ruby client.index('books').reset_dictionary ``` ```go client.Index("books").ResetDictionary() ``` ```csharp await client.Index("books").ResetDictionaryAsync(); ``` ```rust let task: TaskInfo = client .index('books') .reset_dictionary() .await .unwrap(); ``` ```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 \ -X GET 'MEILISEARCH_URL/indexes/movies/settings/displayed-attributes' ``` ```js client.index('movies').getDisplayedAttributes() ``` ```py client.index('movies').get_displayed_attributes() ``` ```php $client->index('movies')->getDisplayedAttributes(); ``` ```java client.index("movies").getDisplayedAttributesSettings(); ``` ```ruby client.index('movies').get_displayed_attributes ``` ```go client.Index("movies").GetDisplayedAttributes() ``` ```csharp await client.Index("movies").GetDisplayedAttributesAsync(); ``` ```rust let displayed_attributes: Vec = client .index("movies") .get_displayed_attributes() .await .unwrap(); ``` ```swift client.index("movies").getDisplayedAttributes { (result) in switch result { case .success(let displayedAttributes): print(displayedAttributes) case .failure(let error): print(error) } } ``` ```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 \ -X PUT 'MEILISEARCH_URL/indexes/movies/settings/displayed-attributes' \ -H 'Content-Type: application/json' \ --data-binary '[ "title", "overview", "genres", "release_date" ]' ``` ```js client.index('movies').updateDisplayedAttributes([ 'title', 'overview', 'genres', 'release_date' ]) ``` ```py client.index('movies').update_displayed_attributes([ 'title', 'overview', 'genres', 'release_date' ]) ``` ```php $client->index('movies')->updateDisplayedAttributes([ 'title', 'overview', 'genres', 'release_date' ]); ``` ```java client.index("movies").updateDisplayedAttributesSettings(new String[] { "title", "overview", "genres", "release_date" }); ``` ```ruby client.index('movies').update_displayed_attributes([ 'title', 'overview', 'genres', 'release_date' ]) ``` ```go displayedAttributes := []string{ "title", "overview", "genres", "release_date", } client.Index("movies").UpdateDisplayedAttributes(&displayedAttributes) ``` ```csharp await client.Index("movies").UpdateDisplayedAttributesAsync(new[] { "title", "overview", "genres", "release_date" }); ``` ```rust let displayed_attributes = [ "title", "overview", "genres", "release_date" ]; let task: TaskInfo = client .index("movies") .set_displayed_attributes(&displayed_attributes) .await .unwrap(); ``` ```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 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 \ -X DELETE 'MEILISEARCH_URL/indexes/movies/settings/displayed-attributes' ``` ```js client.index('movies').resetDisplayedAttributes() ``` ```py client.index('movies').reset_displayed_attributes() ``` ```php $client->index('movies')->resetDisplayedAttributes(); ``` ```java client.index("movies").resetDisplayedAttributesSettings(); ``` ```ruby client.index('movies').reset_displayed_attributes ``` ```go client.Index("movies").ResetDisplayedAttributes() ``` ```csharp await client.Index("movies").ResetDisplayedAttributesAsync(); ``` ```rust let task: TaskInfo = client .index("movies") .reset_displayed_attributes() .await .unwrap(); ``` ```swift client.index("movies").resetDisplayedAttributes { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X GET 'MEILISEARCH_URL/indexes/shoes/settings/distinct-attribute' ``` ```js client.index('shoes').getDistinctAttribute() ``` ```py client.index('shoes').get_distinct_attribute() ``` ```php $client->index('shoes')->getDistinctAttribute(); ``` ```java client.index("shoes").getDistinctAttributeSettings(); ``` ```ruby client.index('shoes').distinct_attribute ``` ```go client.Index("shoes").GetDistinctAttribute() ``` ```csharp string result = await client.Index("shoes").GetDistinctAttributeAsync(); ``` ```rust let distinct_attribute: Option = client .index("shoes") .get_distinct_attribute() .await .unwrap(); ``` ```swift client.index("shoes").getDistinctAttribute { (result) in switch result { case .success(let distinctAttribute): print(distinctAttribute) case .failure(let error): print(error) } } ``` ```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 \ -X PUT 'MEILISEARCH_URL/indexes/shoes/settings/distinct-attribute' \ -H 'Content-Type: application/json' \ --data-binary '"skuid"' ``` ```js client.index('shoes').updateDistinctAttribute('skuid') ``` ```py client.index('shoes').update_distinct_attribute('skuid') ``` ```php $client->index('shoes')->updateDistinctAttribute('skuid'); ``` ```java client.index("shoes").updateDistinctAttributeSettings("skuid"); ``` ```ruby client.index('shoes').update_distinct_attribute('skuid') ``` ```go client.Index("shoes").UpdateDistinctAttribute("skuid") ``` ```csharp TaskInfo result = await client.Index("shoes").UpdateDistinctAttributeAsync("skuid"); ``` ```rust let task: TaskInfo = client .index("shoes") .set_distinct_attribute("skuid") .await .unwrap(); ``` ```swift client.index("shoes").updateDistinctAttribute("skuid") { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X DELETE 'MEILISEARCH_URL/indexes/shoes/settings/distinct-attribute' ``` ```js client.index('shoes').resetDistinctAttribute() ``` ```py client.index('shoes').reset_distinct_attribute() ``` ```php $client->index('shoes')->resetDistinctAttribute(); ``` ```java client.index("shoes").resetDistinctAttributeSettings(); ``` ```ruby client.index('shoes').reset_distinct_attribute ``` ```go client.Index("shoes").ResetDistinctAttribute() ``` ```csharp TaskInfo result = await client.Index("shoes").ResetDistinctAttributeAsync(); ``` ```rust let task: TaskInfo = client .index("shoes") .reset_distinct_attribute() .await .unwrap(); ``` ```swift client.index("shoes").resetDistinctAttribute { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X GET 'MEILISEARCH_URL/indexes/books/settings/faceting' ``` ```js client.index('books').getFaceting() ``` ```py client.index('books').get_faceting_settings() ``` ```php $client->index('books')->getFaceting(); ``` ```java client.index("books").getFacetingSettings(); ``` ```ruby client.index('books').faceting ``` ```go client.Index("books").GetFaceting() ``` ```csharp await client.Index("movies").GetFacetingAsync(); ``` ```rust let faceting: FacetingSettings = client .index("books") .get_faceting() .await .unwrap(); ``` ```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 \ -X PATCH 'MEILISEARCH_URL/indexes/books/settings/faceting' \ -H 'Content-Type: application/json' \ --data-binary '{ "maxValuesPerFacet": 2, "sortFacetValuesBy": { "*": "alpha", "genres": "count" } }' ``` ```js client.index('books').updateFaceting({ maxValuesPerFacet: 2 sortFacetValuesBy: { '*': 'alpha', genres: 'count' } }) ``` ```py params = { 'maxValuesPerFacet': 2, 'sortFacetValuesBy': { '*': 'count', 'genres': 'count' } } client.index('books').update_faceting_settings(params) ``` ```php $client->index('books')->updateFaceting([ 'maxValuesPerFacet' => 2, 'sortFacetValuesBy' => ['*' => 'alpha', 'genres' => 'count'] ]); ``` ```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 client.index('books').update_faceting({ max_values_per_facet: 2, sort_facet_values_by: { '*': 'alpha', genres: 'count' } }) ``` ```go client.Index("books").UpdateFaceting(&meilisearch.Faceting{ MaxValuesPerFacet: 2, SortFacetValuesBy: { "*": SortFacetTypeAlpha, "genres": SortFacetTypeCount, } }) ``` ```csharp var faceting = new Faceting { MaxValuesPerFacet = 2, SortFacetValuesBy = new Dictionary { ["*"] = SortFacetValuesByType.Alpha, ["genres"] = SortFacetValuesByType.Count } }; await client.Index("books").UpdateFacetingAsync(faceting); ``` ```rust let mut faceting = FacetingSettings { max_values_per_facet: 2, }; let task: TaskInfo = client .index("books") .set_faceting(&faceting) .await .unwrap(); ``` ```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 \ -X DELETE 'MEILISEARCH_URL/indexes/books/settings/faceting' ``` ```js client.index('books').resetFaceting() ``` ```py client.index('books').reset_faceting_settings() ``` ```php $client->index('books')->resetFaceting(); ``` ```java client.index("books").resetFacetingSettings(); ``` ```ruby index('books').reset_faceting ``` ```go client.Index("books").ResetFaceting() ``` ```csharp await client.Index("movies").ResetFacetingAsync(); ``` ```rust let task: TaskInfo = client .index("books") .reset_faceting() .await .unwrap(); ``` ```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 or facets. Updating filterable 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 filterable attributes, refer to our dedicated guide.](/learn/filtering_and_sorting/filter_search_results) #### 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 \ -X GET 'MEILISEARCH_URL/indexes/movies/settings/filterable-attributes' ``` ```js client.index('movies').getFilterableAttributes() ``` ```py client.index('movies').get_filterable_attributes() ``` ```php $client->index('movies')->getFilterableAttributes(); ``` ```java client.index("movies").getFilterableAttributesSettings(); ``` ```ruby client.index('movies').filterable_attributes ``` ```go client.Index("movies").GetFilterableAttributes() ``` ```csharp IEnumerable attributes = await client.Index("movies").GetFilterableAttributesAsync(); ``` ```rust let filterable_attributes: Vec = client .index("movies") .get_filterable_attributes() .await .unwrap(); ``` ```swift client.index("movies").getFilterableAttributes { (result) in switch result { case .success(let attributes): print(attributes) case .failure(let error): print(error) } } ``` ```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. 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"]`. If the field does not exist, no error will be thrown. [To learn more about filterable attributes, refer to our dedicated guide.](/learn/filtering_and_sorting/filter_search_results) ##### Example ```bash curl \ -X PUT 'MEILISEARCH_URL/indexes/movies/settings/filterable-attributes' \ -H 'Content-Type: application/json' \ --data-binary '[ "genres", "director" ]' ``` ```js client.index('movies') .updateFilterableAttributes([ 'genres', 'director' ]) ``` ```py client.index('movies').update_filterable_attributes([ 'genres', 'director' ]) ``` ```php $client->index('movies')->updateFilterableAttributes([ 'genres', 'director' ]); ``` ```java Settings settings = new Settings(); settings.setFilterableAttributes(new String[] {"genres", "director"}); client.index("movies").updateSettings(settings); ``` ```ruby client.index('movies').update_filterable_attributes([ 'genres', 'director' ]) ``` ```go filterableAttributes := []string{ "genres", "director", } client.Index("movies").UpdateFilterableAttributes(&filterableAttributes) ``` ```csharp List attributes = new() { "genres", "director" }; TaskInfo result = await client.Index("movies").UpdateFilterableAttributesAsync(attributes); ``` ```rust let filterable_attributes = [ "genres", "director" ]; let task: TaskInfo = client .index("movies") .set_filterable_attributes(&filterable_attributes) .await .unwrap(); ``` ```swift client.index("movies").updateFilterableAttributes(["genre", "director"]) { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X DELETE 'MEILISEARCH_URL/indexes/movies/settings/filterable-attributes' ``` ```js client.index('movies').resetFilterableAttributes() ``` ```py client.index('movies').reset_filterable_attributes() ``` ```php $client->index('movies')->resetFilterableAttributes(); ``` ```java client.index("movies").resetFilterableAttributesSettings(); ``` ```ruby client.index('movies').reset_filterable_attributes ``` ```go client.Index("movies").ResetFilterableAttributes() ``` ```csharp TaskInfo result = await client.Index("movies").ResetFilterableAttributesAsync(); ``` ```rust let task: TaskInfo = client .index("movies") .reset_filterable_attributes() .await .unwrap(); ``` ```swift client.index("movies").resetFilterableAttributes { (result) in switch result { case .success(let attributes): print(attributes) case .failure(let error): print(error) } } ``` ```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 | | **`attribute_patterns`** | 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 \ -X GET 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/localized-attributes' ``` ```js client.index('INDEX_NAME').getLocalizedAttributes() ``` ```py client.index('INDEX_NAME').get_localized_attributes() ``` ```php $client->index('INDEX_NAME')->getLocalizedAttributes(); ``` ```java client.index("INDEX_NAME").getLocalizedAttributesSettings(); ``` ```ruby client.index('INDEX_NAME').localized_attributes ``` ```go client.index("INDEX_NAME").GetLocalizedAttributes() ``` ```rust let localized_attributes: Option> = client .index("books") .get_localized_attributes() .await .unwrap(); ``` ###### Response: `200 OK` ```json { "localizedAttributes": [ {"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 ``` { "localizedAttributes": [ { "locales": [, …], "attributePatterns": [, …], } ] } ``` | Name | Type | Default value | Description | | :----------------- | :------ | :------------ | :---------------------------------------------------------- | | **`localizedAttributes`** | Array of objects | `[]` | Explicitly set specific locales for one or more attributes | ##### Example ```bash curl \ -X PUT 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/localized-attributes' \ -H 'Content-Type: application/json' \ --data-binary '{ "localizedAttributes": [ {"locales": ["jpn"], "attributePatterns": ["*_ja"]} ] }' ``` ```js client.index('INDEX_NAME').updateLocalizedAttributes([ { attributePatterns: ['*_ja'], locales: ['jpn'] }, ]) ``` ```py client.index('INDEX_NAME').update_localized_attributes([ {'attribute_patterns': ['*_ja'], 'locales': ['jpn']} ]) ``` ```php $client->index('INDEX_NAME')->updateLocalizedAttributes([ 'locales' => ['jpn'], 'attributePatterns' => ['*_ja'] ]); ``` ```java LocalizedAttribute attribute = new LocalizedAttribute(); attribute.setAttributePatterns(new String[] {"jpn"}); attribute.setLocales(new String[] {"*_ja"}); client.index("INDEX_NAME").updateLocalizedAttributesSettings( new LocalizedAttributes[] {attribute} ); ``` ```ruby client.index('INDEX_NAME').update_localized_attributes([ { attribute_patterns: ['*_ja'], locales: ['jpn'] }, ]) ``` ```go client.index("INDEX_NAME").UpdateLocalizedAttributes([]*LocalizedAttributes{ { AttributePatterns: ["*_ja"], Locales: ["jpn"] }, }) ``` ```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 \ -X DELETE 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/localized-attributes' ``` ```js client.index('INDEX_NAME').resetLocalizedAttributes() ``` ```py client.index('INDEX_NAME').reset_localized_attributes() ``` ```php $client->index('INDEX_NAME')->resetLocalizedAttributes(); ``` ```java client.index("INDEX_NAME").resetLocalizedAttributesSettings(); ``` ```ruby client.index('INDEX_NAME').reset_localized_attributes ``` ```go client.index("INDEX_NAME").ResetLocalizedAttributes() ``` ```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 | #### 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 \ -X GET 'MEILISEARCH_URL/indexes/books/settings/pagination' ``` ```js client.index('books').getPagination() ``` ```py client.index('books').get_pagination_settings() ``` ```php $client->index('books')->getPagination(); ``` ```java client.index("books").getPaginationSettings(); ``` ```ruby index('books').pagination ``` ```go client.Index("books").GetPagination() ``` ```csharp await client.Index("movies").GetPaginationAsync(); ``` ```rust let pagination: PaginationSetting = client .index("books") .get_pagination() .await .unwrap(); ``` ```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 \ -X PATCH 'MEILISEARCH_URL/indexes/books/settings/pagination' \ -H 'Content-Type: application/json' \ --data-binary '{ "maxTotalHits": 100 }' ``` ```js client.index('books').updateSettings({ pagination: { maxTotalHits: 100 }}) ``` ```py client.index('books').update_pagination_settings({'maxTotalHits': 100}) ``` ```php $client->index('books')->updateSettings([ 'pagination' => [ 'maxTotalHits' => 100 ] ]); ``` ```java Pagination newPagination = new Pagination(); newPagination.setMaxTotalHits(100); client.index("books").updatePaginationSettings(newPagination); ``` ```ruby index('books').update_pagination({ max_total_hits: 100 }) ``` ```go client.Index("books").UpdatePagination(&meilisearch.Pagination{ MaxTotalHits: 100, }) ``` ```csharp var pagination = new Pagination { MaxTotalHits = 20 }; await client.Index("movies").UpdatePaginationAsync(pagination); ``` ```rust let pagination = PaginationSetting {max_total_hits:100}; let task: TaskInfo = client .index("books") .set_pagination(pagination) .await .unwrap(); ``` ```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 \ -X DELETE 'MEILISEARCH_URL/indexes/books/settings/pagination' ``` ```js client.index('books').resetPagination() ``` ```py client.index('books').reset_pagination_settings() ``` ```php $client->index('books')->resetPagination(); ``` ```java client.index("books").resetPaginationSettings(); ``` ```ruby index('books').reset_pagination ``` ```go client.Index("books").ResetPagination() ``` ```csharp await client.Index("movies").ResetPaginationAsync(); ``` ```rust let task: TaskInfo = client .index("books") .reset_pagination() .await .unwrap(); ``` ```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 \ -X GET 'MEILISEARCH_URL/indexes/books/settings/proximity-precision' ``` ```js client.index('books').getProximityPrecision() ``` ```py client.index('books').get_proximity_precision() ``` ```php $client->index('books')->getProximityPrecision(); ``` ```java client.index("books").getProximityPrecisionSettings(); ``` ```ruby client.index('books').proximity_precision ``` ```go client.Index("books").GetProximityPrecision() ``` ```csharp await client.Index("books").GetProximityPrecisionAsync(); ``` ```rust let proximity_precision: String = client .index("books") .get_proximity_precision() .await .unwrap(); ``` ```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 \ -X PUT 'MEILISEARCH_URL/indexes/books/settings/proximity-precision' \ -H 'Content-Type: application/json' \ --data-binary '"byAttribute"' ``` ```js client.index('books').updateProximityPrecision('byAttribute') ``` ```py client.index('books').update_proximity_precision(ProximityPrecision.BY_ATTRIBUTE) ``` ```php $client->index('books')->updateProximityPrecision('byAttribute'); ``` ```java client.index("books").updateProximityPrecisionSettings("byAttribute"); ``` ```ruby client.index('books').update_proximity_precision('byAttribute') ``` ```go client.Index("books").UpdateProximityPrecision(ByAttribute) ``` ```csharp await client.Index("books").UpdateProximityPrecisionAsync("byAttribute"); ``` ```rust let task: TaskInfo = client .index("books") .set_proximity_precision("byAttribute".to_string()) .await .unwrap(); ``` ```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 \ -X DELETE 'MEILISEARCH_URL/indexes/books/settings/proximity-precision' ``` ```js client.index('books').resetProximityPrecision() ``` ```py client.index('books').reset_proximity_precision() ``` ```php $client->index('books')->resetProximityPrecision(); ``` ```java client.index("books").resetProximityPrecisionSettings(); ``` ```ruby client.index('books').reset_proximity_precision ``` ```go client.Index("books").ResetProximityPrecision() ``` ```csharp await client.Index("books").ResetProximityPrecisionAsync(); ``` ```rust let task: TaskInfo = client .index("books") .reset_proximity_precision() .await .unwrap(); ``` ```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 \ -X GET 'MEILISEARCH_URL/indexes/INDEX_UID/settings/facet-search' ``` ```js client.index('INDEX_NAME').getFacetSearch(); ``` ```php $client->index('INDEX_NAME')->getFacetSearch(); ``` ```ruby client.index('INDEX_UID').facet_search_setting ``` ###### 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 \ -X PUT 'MEILISEARCH_URL/indexes/INDEX_UID/settings/facet-search' \ -H 'Content-Type: application/json' \ --data-binary 'false' ``` ```js client.index('INDEX_NAME').updateFacetSearch(false); ``` ```php $client->index('INDEX_NAME')->updateFacetSearch(false); ``` ```ruby client.index('INDEX_UID').update_facet_search_setting(false) ``` ###### 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 \ -X DELETE 'MEILISEARCH_URL/indexes/INDEX_UID/settings/facet-search' ``` ```js client.index('INDEX_NAME').resetFacetSearch(); ``` ```php $client->index('INDEX_NAME')->resetFacetSearch(); ``` ```ruby client.index('INDEX_UID').reset_facet_search_setting ``` ###### 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 \ -X GET 'MEILISEARCH_URL/indexes/INDEX_UID/settings/prefix-search' ``` ```js client.index('INDEX_NAME').getPrefixSearch(); ``` ```php $client->index('INDEX_NAME')->getPrefixSearch(); ``` ```ruby client.index('INDEX_UID').prefix_search ``` ###### 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 \ -X PUT 'MEILISEARCH_URL/indexes/INDEX_UID/settings/prefix-search' \ -H 'Content-Type: application/json' \ --data-binary '"disabled"' ``` ```js client.index('INDEX_NAME').updatePrefixSearch('disabled'); ``` ```php $client->index('INDEX_NAME')->updatePrefixSearch('disabled'); ``` ```ruby client.index('INDEX_UID').update_prefix_search('disabled') ``` ###### 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 \ -X DELETE 'MEILISEARCH_URL/indexes/INDEX_UID/settings/facet-search' ``` ```js client.index('INDEX_NAME').resetFacetSearch(); ``` ```php $client->index('INDEX_NAME')->resetFacetSearch(); ``` ```ruby client.index('INDEX_UID').reset_facet_search_setting ``` ###### 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 \ -X GET 'MEILISEARCH_URL/indexes/movies/settings/ranking-rules' ``` ```js client.index('movies').getRankingRules() ``` ```py client.index('movies').get_ranking_rules() ``` ```php $client->index('movies')->getRankingRules(); ``` ```java client.index("movies").getRankingRulesSettings(); ``` ```ruby client.index('movies').ranking_rules ``` ```go client.Index("movies").GetRankingRules() ``` ```csharp await client.Index("movies").GetRankingRulesAsync(); ``` ```rust let ranking_rules: Vec = client .index("movies") .get_ranking_rules() .await .unwrap(); ``` ```swift client.index("movies").getRankingRules { (result) in switch result { case .success(let rankingRules): print(rankingRules) case .failure(let error): print(error) } } ``` ```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 \ -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" ]' ``` ```js client.index('movies').updateRankingRules([ 'words', 'typo', 'proximity', 'attribute', 'sort', 'exactness', 'release_date:asc', 'rank:desc' ]) ``` ```py client.index('movies').update_ranking_rules([ 'words', 'typo', 'proximity', 'attribute', 'sort', 'exactness', 'release_date:asc', 'rank:desc' ]) ``` ```php $client->index('movies')->updateRankingRules([ 'words', 'typo', 'proximity', 'attribute', 'sort', 'exactness', 'release_date:asc', 'rank:desc' ]); ``` ```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 client.index('movies').update_ranking_rules([ 'words', 'typo', 'proximity', 'attribute', 'sort', 'exactness', 'release_date:asc', 'rank:desc' ]) ``` ```go rankingRules := []string{ "words", "typo", "proximity", "attribute", "sort", "exactness", "release_date:asc", "rank:desc", } client.Index("movies").UpdateRankingRules(&rankingRules) ``` ```csharp await client.Index("movies").UpdateRankingRulesAsync(new[] { "words", "typo", "proximity", "attribute", "sort", "exactness", "release_date:asc", "rank:desc" }); ``` ```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 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 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 \ -X DELETE 'MEILISEARCH_URL/indexes/movies/settings/ranking-rules' ``` ```js client.index('movies').resetRankingRules() ``` ```py client.index('movies').reset_ranking_rules() ``` ```php $client->index('movies')->resetRankingRules(); ``` ```java client.index("movies").resetRankingRulesSettings(); ``` ```ruby client.index('movies').reset_ranking_rules ``` ```go client.Index("movies").ResetRankingRules() ``` ```csharp await client.Index("movies").ResetRankingRulesAsync(); ``` ```rust let task: TaskInfo = client .index("movies") .reset_ranking_rules() .await .unwrap(); ``` ```swift client.index("movies").resetRankingRules { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X GET 'MEILISEARCH_URL/indexes/movies/settings/searchable-attributes' ``` ```js client.index('movies').getSearchableAttributes() ``` ```py client.index('movies').get_searchable_attributes() ``` ```php $client->index('movies')->getSearchableAttributes(); ``` ```java client.index("movies").getSearchableAttributesSettings(); ``` ```ruby client.index('movies').searchable_attributes ``` ```go client.Index("movies").GetSearchableAttributes() ``` ```csharp await client.Index("movies").GetSearchableAttributesAsync(); ``` ```rust let searchable_attributes: Vec = client .index("movies") .get_searchable_attributes() .await .unwrap(); ``` ```swift client.index("movies").getSearchableAttributes { (result) in switch result { case .success(let searchableAttributes): print(searchableAttributes) case .failure(let error): print(error) } } ``` ```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 \ -X PUT 'MEILISEARCH_URL/indexes/movies/settings/searchable-attributes' \ -H 'Content-Type: application/json' \ --data-binary '[ "title", "overview", "genres" ]' ``` ```js client.index('movies').updateSearchableAttributes([ 'title', 'overview', 'genres' ]) ``` ```py client.index('movies').update_searchable_attributes([ 'title', 'overview', 'genres' ]) ``` ```php $client->index('movies')->updateSearchableAttributes([ 'title', 'overview', 'genres' ]); ``` ```java client.index("movies").updateSearchableAttributesSettings(new String[] { "title", "overview", "genres" }); ``` ```ruby client.index('movies').update_searchable_attributes([ 'title', 'overview', 'genres' ]) ``` ```go searchableAttributes := []string{ "title", "overview", "genres", } client.Index("movies").UpdateSearchableAttributes(&searchableAttributes) ``` ```csharp await client.Index("movies").UpdateSearchableAttributesAsync(new[] {"title", "overview", "genres"}); ``` ```rust let searchable_attributes = [ "title", "overview", "genres" ]; let task: TaskInfo = client .index("movies") .set_searchable_attributes(&searchable_attributes) .await .unwrap(); ``` ```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 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 \ -X DELETE 'MEILISEARCH_URL/indexes/movies/settings/searchable-attributes' ``` ```js client.index('movies').resetSearchableAttributes() ``` ```py client.index('movies').reset_searchable_attributes() ``` ```php $client->index('movies')->resetSearchableAttributes(); ``` ```java client.index("movies").resetSearchableAttributesSettings(); ``` ```ruby client.index('movies').reset_searchable_attributes ``` ```go client.Index("movies").ResetSearchableAttributes() ``` ```csharp await client.Index("movies").ResetSearchableAttributesAsync(); ``` ```rust let task: TaskInfo = client .index("movies") .reset_searchable_attributes() .await .unwrap(); ``` ```swift client.index("movies").resetSearchableAttributes { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X GET 'MEILISEARCH_URL/indexes/movies/settings/search-cutoff-ms' ``` ```js client.index('movies').getSearchCutoffMs() ``` ```py client.index('movies').get_search_cutoff_ms() ``` ```php $client->index('movies')->getSearchCutoffMs(); ``` ```java client.index("movies").getSearchCutoffMsSettings(); ``` ```ruby client.index('movies').search_cutoff_ms ``` ```go client.Index("movies").GetSearchCutoffMs() ``` ```csharp var searchCutoff = await client.Index("movies").GetSearchCutoffMsAsync(); ``` ```rust let search_cutoff_ms: String = client .index("movies") .get_search_cutoff_ms() .await .unwrap(); ``` ```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 \ -X PUT 'MEILISEARCH_URL/indexes/movies/settings/search-cutoff-ms' \ -H 'Content-Type: application/json' \ --data-binary '150' ``` ```js client.index('movies').updateSearchCutoffMs(150) ``` ```py client.index('movies').update_search_cutoff_ms(150) ``` ```php $client->index('movies')->updateSearchCutoffMs(150); ``` ```java client.index("movies").updateSearchCutoffMsSettings(150); ``` ```ruby client.index('movies').update_search_cutoff_ms(150) ``` ```go client.Index("movies").UpdateSearchCutoffMs(150) ``` ```csharp await client.Index("movies").UpdateSearchCutoffMsAsync(150); ``` ```rust let task: TaskInfo = client .index("movies") .set_search_cutoff_ms(Some(150)) .await .unwrap(); ``` ```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 \ -X DELETE 'MEILISEARCH_URL/indexes/movies/settings/search-cutoff-ms' ``` ```js client.index('movies').resetSearchCutoffMs() ``` ```py client.index('movies').reset_search_cutoff_ms() ``` ```php $client->index('movies')->resetSearchCutoffMs(); ``` ```java client.index("movies").resetSearchCutoffMsSettings(); ``` ```ruby client.index('movies').reset_search_cutoff_ms ``` ```go client.Index("books").ResetSearchCutoffMs() ``` ```csharp await client.Index("movies").ResetSearchCutoffMsAsync(); ``` ```rust let task: TaskInfo = client .index("movies") .reset_search_cutoff_ms() .await .unwrap(); ``` ```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 \ -X GET 'MEILISEARCH_URL/indexes/articles/settings/separator-tokens' ``` ```js client.index('books').getSeparatorTokens() ``` ```py client.index('articles').get_separator_tokens() ``` ```php $client->index('articles')->getSeparatorTokens(); ``` ```java client.index("articles").getSeparatorTokensSettings(); ``` ```ruby client.index('articles').separator_tokens ``` ```go client.Index("articles").GetSeparatorTokens() ``` ```csharp await client.Index("movies").GetSeparatorTokensAsync(); ``` ```rust let task: TaskInfo = client .index('articles') .get_separator_tokens() .await .unwrap(); ``` ```swift client.index("books").getSeparatorTokens { result in // handle result } ``` ```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 \ -X PUT 'MEILISEARCH_URL/indexes/articles/settings/separator-tokens' \ -H 'Content-Type: application/json' \ --data-binary '["|", "…"]' ``` ```js client.index('books').updateSeparatorTokens(['|', '…']) ``` ```py client.index('articles').update_separator_tokens(["|", "…"]) ``` ```php $client->index('articles')->updateSeparatorTokens(['|', '…']); ``` ```java String[] newSeparatorTokens = { "|", "…" }; client.index("articles").updateSeparatorTokensSettings(newSeparatorTokens); ``` ```ruby client.index('articles').update_separator_tokens(['|', '…']) ``` ```go client.Index("articles").UpdateSeparatorTokens([]string{ "|", "…", }) ``` ```csharp await client.Index("movies").UpdateSeparatorTokensAsync(new[] { "|", "…" }); ``` ```rust let task: TaskInfo = client .index('articles') .set_separator_tokens(&vec!['|'.to_string(), '…'.to_string()]) .await .unwrap(); ``` ```swift client.index("books").updateSeparatorTokens(["|", "…"]) { result in // handle result } ``` ```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 \ -X DELETE 'MEILISEARCH_URL/indexes/articles/settings/separator-tokens' ``` ```js client.index('books').resetSeparatorTokens() ``` ```py client.index('articles').reset_separator_tokens() ``` ```php $client->index('articles')->resetSeparatorTokens(); ``` ```java client.index("articles").resetSeparatorTokensSettings(); ``` ```ruby client.index('articles').reset_separator_tokens ``` ```go client.Index("articles").ResetSeparatorTokens() ``` ```csharp await client.Index("movies").ResetSeparatorTokensAsync(); ``` ```rust let task: TaskInfo = client .index('articles') .reset_separator_tokens() .await .unwrap(); ``` ```swift client.index("books").resetSeparatorTokens { result in // handle result } ``` ```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 \ -X GET 'MEILISEARCH_URL/indexes/articles/settings/non-separator-tokens' ``` ```js client.index('books').getNonSeparatorTokens() ``` ```py client.index('articles').get_non_separator_tokens() ``` ```php $client->index('articles')->getNonSeparatorTokens(); ``` ```java client.index("articles").getNonSeparatorTokensSettings(); ``` ```ruby client.index('articles').non_separator_tokens ``` ```go client.Index("articles").GetNonSeparatorTokens() ``` ```csharp await client.Index("movies").GetNonSeparatorTokensAsync(); ``` ```rust let task: TaskInfo = client .index('articles') .get_non_separator_tokens() .await .unwrap(); ``` ```swift client.index("books").getNonSeparatorTokens { result in // handle result } ``` ```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 \ -X PUT 'MEILISEARCH_URL/indexes/articles/settings/non-separator-tokens' \ -H 'Content-Type: application/json' \ --data-binary '["@", "#"]' ``` ```js client.index('books').updateNonSeparatorTokens(['@', '#']) ``` ```py client.index('articles').update_non_separator_tokens(["@", "#"]) ``` ```php $client->index('articles')->updateNonSeparatorTokens(['@', '#']); ``` ```java String[] newSeparatorTokens = { "@", "#" }; client.index("articles").updateNonSeparatorTokensSettings(newSeparatorTokens); ``` ```ruby client.index('articles').update_non_separator_tokens(['@', '#']) ``` ```go client.Index("articles").UpdateNonSeparatorTokens([]string{ "@", "#", }) ``` ```csharp await client.Index("movies").UpdateNonSeparatorTokensAsync(new[] { "@", "#" }); ``` ```rust let task: TaskInfo = client .index('articles') .set_non_separator_tokens(&vec!['@'.to_string(), '#'.to_string()]) .await .unwrap(); ``` ```swift client.index("books").updateNonSeparatorTokens(["@", "#"]) { result in // handle result } ``` ```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 \ -X DELETE 'MEILISEARCH_URL/indexes/articles/settings/separator-tokens' ``` ```js client.index('books').resetSeparatorTokens() ``` ```py client.index('articles').reset_separator_tokens() ``` ```php $client->index('articles')->resetSeparatorTokens(); ``` ```java client.index("articles").resetSeparatorTokensSettings(); ``` ```ruby client.index('articles').reset_separator_tokens ``` ```go client.Index("articles").ResetSeparatorTokens() ``` ```csharp await client.Index("movies").ResetSeparatorTokensAsync(); ``` ```rust let task: TaskInfo = client .index('articles') .reset_separator_tokens() .await .unwrap(); ``` ```swift client.index("books").resetSeparatorTokens { result in // handle result } ``` ```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 \ -X GET 'MEILISEARCH_URL/indexes/books/settings/sortable-attributes' ``` ```js client.index('books').getSortableAttributes() ``` ```py client.index('books').get_sortable_attributes() ``` ```php $client->index('books')->getSortableAttributes(); ``` ```java client.index("books").getSortableAttributesSettings(); ``` ```ruby client.index('books').sortable_attributes ``` ```go client.Index("books").GetSortableAttributes() ``` ```csharp await client.Index("books").GetSortableAttributesAsync(); ``` ```rust let sortable_attributes: Vec = client .index("books") .get_sortable_attributes() .await .unwrap(); ``` ```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 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 \ -X PUT 'MEILISEARCH_URL/indexes/books/settings/sortable-attributes' \ -H 'Content-Type: application/json' \ --data-binary '[ "price", "author" ]' ``` ```js client.index('books') .updateSortableAttributes([ 'price', 'author' ]) ``` ```py client.index('books').update_sortable_attributes([ 'price', 'author' ]) ``` ```php $client->index('books')->updateSortableAttributes([ 'price', 'author' ]); ``` ```java client.index("books").updateSortableAttributesSettings(new String[] {"price", "author"}); ``` ```ruby client.index('books').update_sortable_attributes([ 'price', 'author' ]) ``` ```go sortableAttributes := []string{ "price", "author", } client.Index("books").UpdateSortableAttributes(&sortableAttributes) ``` ```csharp await client.Index("books").UpdateSortableAttributesAsync(new [] { "price", "author" }); ``` ```rust let sortable_attributes = [ "price", "author" ]; let task: TaskInfo = client .index("books") .set_sortable_attributes(&sortable_attributes) .await .unwrap(); ``` ```swift client.index("books").updateSortableAttributes(["price", "author"]) { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X DELETE 'MEILISEARCH_URL/indexes/books/settings/sortable-attributes' ``` ```js client.index('books').resetSortableAttributes() ``` ```py client.index('books').reset_sortable_attributes() ``` ```php $client->index('books')->resetSortableAttributes(); ``` ```java client.index("books").resetSortableAttributesSettings(); ``` ```ruby client.index('books').reset_sortable_attributes ``` ```go client.Index("books").ResetSortableAttributes() ``` ```csharp await client.Index("books").ResetSortableAttributesAsync(); ``` ```rust let task: TaskInfo = client .index("books") .reset_sortable_attributes() .await .unwrap(); ``` ```swift client.index("books").resetSortableAttributes() { (result) in switch result { case .success(let attributes): print(attributes) case .failure(let error): print(error) } } ``` ```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 website maintained by a French developer](https://sites.google.com/site/kevinbouge/stopwords-lists) 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 \ -X GET 'MEILISEARCH_URL/indexes/movies/settings/stop-words' ``` ```js client.index('movies').getStopWords() ``` ```py client.index('movies').get_stop_words() ``` ```php $client->index('movies')->getStopWords(); ``` ```java client.index("movies").getStopWordsSettings(); ``` ```ruby client.index('movies').stop_words ``` ```go client.Index("movies").GetStopWords() ``` ```csharp await client.Index("movies").GetStopWordsAsync(); ``` ```rust let stop_words: Vec = client .index("movies") .get_stop_words() .await .unwrap(); ``` ```swift client.index("movies").getStopWords { (result) in switch result { case .success(let stopWords): print(stopWords) case .failure(let error): print(error) } } ``` ```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 \ -X PUT 'MEILISEARCH_URL/indexes/movies/settings/stop-words' \ -H 'Content-Type: application/json' \ --data-binary '[ "the", "of", "to" ]' ``` ```js client.index('movies').updateStopWords(['of', 'the', 'to']) ``` ```py client.index('movies').update_stop_words(['of', 'the', 'to']) ``` ```php $client->index('movies')->updateStopWords(['the', 'of', 'to']); ``` ```java client.index("movies").updateStopWordsSettings(new String[] {"of", "the", "to"}); ``` ```ruby client.index('movies').update_stop_words(['of', 'the', 'to']) ``` ```go stopWords := []string{"of", "the", "to"} client.Index("movies").UpdateStopWords(&stopWords) ``` ```csharp await client.Index("movies").UpdateStopWordsAsync(new[] {"of", "the", "to"}); ``` ```rust let stop_words = ["of", "the", "to"]; let task: TaskInfo = client .index("movies") .set_stop_words(&stop_words) .await .unwrap(); ``` ```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 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 \ -X DELETE 'MEILISEARCH_URL/indexes/movies/settings/stop-words' ``` ```js client.index('movies').resetStopWords() ``` ```py client.index('movies').reset_stop_words() ``` ```php $client->index('movies')->resetStopWords(); ``` ```java client.index("movies").resetStopWordsSettings(); ``` ```ruby client.index('movies').reset_stop_words ``` ```go client.Index("movies").ResetStopWords() ``` ```csharp await client.Index("movies").ResetStopWordsAsync(); ``` ```rust let task: TaskInfo = client .index("movies") .reset_stop_words() .await .unwrap(); ``` ```swift client.index("movies").resetStopWords { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 \ -X GET 'MEILISEARCH_URL/indexes/movies/settings/synonyms' ``` ```js client.index('movies').getSynonyms() ``` ```py client.index('movies').get_synonyms() ``` ```php $client->index('movies')->getSynonyms(); ``` ```java client.index("movies").getSynonymsSettings(); ``` ```ruby client.index('movies').synonyms ``` ```go client.Index("movies").GetSynonyms() ``` ```csharp await client.Index("movies").GetSynonymsAsync(); ``` ```rust let synonyms: HashMap> = client .index("movies") .get_synonyms() .await .unwrap(); ``` ```swift client.index("movies").getSynonyms { (result) in switch result { case .success(let synonyms): print(synonyms) case .failure(let error): print(error) } } ``` ```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 \ -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"] }' ``` ```js client.index('movies').updateSynonyms({ wolverine: ['xmen', 'logan'], logan: ['wolverine', 'xmen'], wow: ['world of warcraft'] }) ``` ```py client.index('movies').update_synonyms({ 'wolverine': ['xmen', 'logan'], 'logan': ['wolverine', 'xmen'], 'wow': ['world of warcraft'] }) ``` ```php $client->index('movies')->updateSynonyms([ 'wolverine' => ['xmen', 'logan'], 'logan' => ['wolverine', 'xmen'], 'wow' => ['world of warcraft'] ]); ``` ```java HashMap synonyms = new HashMap(); synonyms.put("wolverine", new String[] {"xmen", "logan"}); synonyms.put("logan", new String[] {"wolverine"}); client.index("movies").updateSynonymsSettings(synonyms); ``` ```ruby client.index('movies').update_synonyms({ wolverine: ['xmen', 'logan'], logan: ['wolverine', 'xmen'], wow: ['world of warcraft'] }) ``` ```go synonyms := map[string][]string{ "wolverine": []string{"xmen", "logan"}, "logan": []string{"wolverine", "xmen"}, "wow": []string{"world of warcraft"}, } client.Index("movies").UpdateSynonyms(&synonyms) ``` ```csharp 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 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 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 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 \ -X DELETE 'MEILISEARCH_URL/indexes/movies/settings/synonyms' ``` ```js client.index('movies').resetSynonyms() ``` ```py client.index('movies').reset_synonyms() ``` ```php $client->index('movies')->resetSynonyms(); ``` ```java client.index("movies").resetSynonymsSettings(); ``` ```ruby client.index('movies').reset_synonyms ``` ```go client.Index("movies").ResetSynonyms() ``` ```csharp await client.Index("movies").ResetSynonymsAsync(); ``` ```rust let task: TaskInfo = client .index("movies") .reset_synonyms() .await .unwrap(); ``` ```swift client.index("movies").resetSynonyms { (result) in switch result { case .success(let task): print(task) case .failure(let error): print(error) } } ``` ```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 | #### 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 \ -X GET 'MEILISEARCH_URL/indexes/books/settings/typo-tolerance' ``` ```js client.index('books').getTypoTolerance() ``` ```py client.index('books').get_typo_tolerance() ``` ```php $client->index('books')->getTypoTolerance(); ``` ```java client.index("books").getTypoToleranceSettings(); ``` ```ruby index('books').typo_tolerance ``` ```go client.Index("books").GetTypoTolerance() ``` ```csharp await client.Index("books").GetTypoToleranceAsync(); ``` ```rust let typo_tolerance: TypoToleranceSettings = client .index("books") .get_typo_tolerance() .await .unwrap(); ``` ```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": [, , …] } ``` | 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 | ##### Example ```bash curl \ -X PATCH 'MEILISEARCH_URL/indexes/books/settings/typo-tolerance' \ -H 'Content-Type: application/json' \ --data-binary '{ "minWordSizeForTypos": { "oneTypo": 4, "twoTypos": 10 }, "disableOnAttributes": ["title"] }' ``` ```js client.index('books').updateTypoTolerance({ minWordSizeForTypos: { oneTypo: 4, twoTypos: 10 }, disableOnAttributes: [ 'title' ] }) ``` ```py client.index('books').update_typo_tolerance({ 'minWordSizeForTypos': { 'oneTypo': 4, 'twoTypos': 10 }, 'disableOnAttributes': [ 'title' ] }) ``` ```php $client->index('books')->updateTypoTolerance([ 'minWordSizeForTypos' => [ 'oneTypo' => 4, 'twoTypos' => 10 ], 'disableOnAttributes' => [ 'title' ] ]); ``` ```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 index('books').update_typo_tolerance({ min_word_size_for_typos: { one_typo: 4, two_typos: 10 }, disable_on_attributes: ['title'] }) ``` ```go client.Index("books").UpdateTypoTolerance(&meilisearch.TypoTolerance{ MinWordSizeForTypos: meilisearch.MinWordSizeForTypos{ OneTypo: 4, TwoTypos: 10, }, DisableOnAttributes: []string{"title"}, }) ``` ```csharp var typoTolerance = new TypoTolerance { DisableOnAttributes = new string[] { "title" }, MinWordSizeTypos = new TypoTolerance.TypoSize { OneTypo = 4, TwoTypos = 10 } }; await client.Index("books").UpdateTypoToleranceAsync(typoTolerance); ``` ```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 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 \ -X DELETE 'MEILISEARCH_URL/indexes/books/settings/typo-tolerance' ``` ```js client.index('books').resetTypoTolerance() ``` ```py client.index('books').reset_typo_tolerance() ``` ```php $client->index('books')->resetTypoTolerance(); ``` ```java client.index("books").resetTypoToleranceSettings(); ``` ```ruby index('books').reset_typo_tolerance ``` ```go client.Index("books").ResetTypoTolerance() ``` ```csharp await client.Index("books").ResetTypoToleranceAsync(); ``` ```rust let task: TaskInfo = client .index("books") .reset_typo_tolerance() .await .unwrap(); ``` ```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 (experimental) 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 request Meilisearch expects from the remote embedder | | **`binaryQuantized`** | Boolean | Empty | Once set to `true`, irreversibly converts all vector dimensions to 1-bit values | #### 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 \ -X GET 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/embedders' ``` ```ruby client.index('INDEX_NAME').embedders ``` ###### 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": } } ``` Set an embedder to `null` to remove it from the embedders list. ###### `source` Use `source` to configure an embedder's source. The following embedders can auto-generate vectors for documents and queries: - `openAi` - `huggingFace` - `ollama` Additionally, use `rest` to auto-generate embeddings with any embedder offering a REST API. You may also configure a `userProvided` embedder. In this case, you must manually include vector data in your documents' `_vectors` field. You must also manually generate vectors for search queries. This field is mandatory. ###### `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. 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`, Meilisearch will attempt to read it from environment variables `OPENAI_API_KEY` and `MEILI_OLLAMA_URL`, respectively. 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 optional but strongly encouraged for all 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 optional for all 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. ##### Example ```bash curl \ -X PATCH 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/embedders' \ -H 'Content-Type: application/json' \ --data-binary '{ "default": { "source": "openAi", "apiKey": "anOpenAiApiKey", "model": "text-embedding-3-small", "documentTemplate": "A document titled '{{doc.title}}' whose description starts with {{doc.overview|truncatewords: 20}}" } }' ``` ```ruby client.index('INDEX_NAME').update_embedders( default: { source: 'openAi', api_key: 'anOpenAiApiKey', model: 'text-embedding-3-small', document_template: "A document titled '{{doc.title}}' whose description starts with {{doc.overview|truncatewords: 20}}" } ) ``` ###### 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 \ -X DELETE 'MEILISEARCH_URL/indexes/INDEX_NAME/settings/embedders' ``` ```ruby client.index('INDEX_NAME').reset_embedders ``` ###### 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). --- title: Snapshots — Meilisearch API reference description: The /snapshots route creates database snapshots. Use snapshots to backup your Meilisearch data. --- ## Snapshots The `/snapshot` route allows you to create database snapshots. Snapshots are `.snapshot` files that can be used to make quick backups of Meilisearch data. [Learn more about snapshots.](/learn/advanced/snapshots) Meilisearch Cloud does not support the `/snapshots` route. ### Create a snapshot Triggers a snapshot creation task. Once the process is complete, Meilisearch creates a snapshot in the [snapshot directory](/learn/self_hosted/configure_meilisearch_at_launch#snapshot-destination). If the snapshot directory does not exist yet, it will be created. Snapshot tasks take priority over other tasks in the queue. [Learn more about asynchronous operations](/learn/async/asynchronous_operations). #### Example ```bash curl \ -X POST 'MEILISEARCH_URL/snapshots' ``` ```js client.createSnapshot() ``` ```py client.create_snapshot() ``` ```php $client->createSnapshot(); ``` ```java client.createSnapshot(); ``` ```ruby client.create_snapshot ``` ```go client.CreateSnapshot() ``` ```csharp await client.CreateSnapshotAsync(); ``` ```rust client .create_snapshot() .await .unwrap(); ``` ```swift let task = try await self.client.createSnapshot() ``` ##### Response: `202 Accepted` ```json { "taskUid": 1, "indexUid": null, "status": "enqueued", "type": "snapshotCreation", "enqueuedAt": "2023-06-21T11:09:36.417758Z" } ``` You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task) --- title: Stats — Meilisearch API reference description: The /stats route you gives extended information and metrics about indexes and the Meilisearch database. --- ## Stats The `/stats` route gives extended information and metrics about indexes and the Meilisearch database. ### Stats object ```json { "databaseSize": 447819776, "lastUpdate": "2019-11-15T11:15:22.092896Z", "indexes": { "movies": { "numberOfDocuments": 19654, "isIndexing": false, "fieldDistribution": { "poster": 19654, "overview": 19654, "title": 19654, "id": 19654, "release_date": 19654 } }, "books": { "numberOfDocuments": 5, "isIndexing": false, "fieldDistribution": { "id": 5, "title": 5, "author": 5, "price": 5, "genres": 5 } } } } ``` | Name | Type | Description | | :---------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`databaseSize`** | Integer | Size of the database in bytes | | **`lastUpdate`** | String | When the last update was made to the database in the [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format | | **`indexes`** | Object | Object containing the statistics for each index found in the database | | **`numberOfDocuments`** | Integer | Total number of documents in an index | | **`isIndexing`** | Boolean | If `true`, the index is still processing documents and attempts to search will result in undefined behavior. If `false`, the index has finished processing and you can start searching | | **`fieldDistribution`** | Object | Shows every field in the index along with the total number of documents containing that field in said index | `fieldDistribution` is not impacted by `searchableAttributes` or `displayedAttributes`. Even if a field is not displayed or searchable, it will still appear in the `fieldDistribution` object. ### Get stats of all indexes Get stats of all indexes. #### Example ```bash curl \ -X GET 'MEILISEARCH_URL/stats' ``` ```js client.getStats() ``` ```py client.get_all_stats() ``` ```php $client->stats(); ``` ```java client.getStats(); ``` ```ruby client.stats ``` ```go client.GetStats() ``` ```csharp await client.GetStatsAsync(); ``` ```rust let stats: ClientStats = client .get_stats() .await .unwrap(); ``` ```swift client.allStats { (result) in switch result { case .success(let stats): print(stats) case .failure(let error): print(error) } } ``` ```dart await client.getStats(); ``` ##### Response: `200 Ok` ```json { "databaseSize": 447819776, "lastUpdate": "2019-11-15T11:15:22.092896Z", "indexes": { "movies": { "numberOfDocuments": 19654, "isIndexing": false, "fieldDistribution": { "poster": 19654, "overview": 19654, "title": 19654, "id": 19654, "release_date": 19654 } }, "books": { "numberOfDocuments": 5, "isIndexing": false, "fieldDistribution": { "id": 5, "title": 5, "author": 5, "price": 5, "genres": 5 } } } } ``` ### Get stats of an index Get stats 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 \ -X GET 'MEILISEARCH_URL/indexes/movies/stats' ``` ```js client.index('movies').getStats() ``` ```py client.index('movies').get_stats() ``` ```php $client->index('movies')->stats(); ``` ```java client.index("movies").getStats(); ``` ```ruby client.index('movies').stats ``` ```go client.Index("movies").GetStats() ``` ```csharp await client.Index("movies").GetStatsAsync(); ``` ```rust let stats: IndexStats = client .index("movies") .get_stats() .await .unwrap(); ``` ```swift client.index("movies").stats { (result) in switch result { case .success(let stats): print(stats) case .failure(let error): print(error) } } ``` ```dart await client.index('movies').getStats(); ``` ##### Response: `200 Ok` ```json { "numberOfDocuments": 19654, "isIndexing": false, "fieldDistribution": { "poster": 19654, "release_date": 19654, "title": 19654, "id": 19654, "overview": 19654 } } ``` --- title: Health — Meilisearch API reference description: The /health route allows you to verify the status and availability of a Meilisearch instance. --- ## Health The `/health` route allows you to verify the status and availability of a Meilisearch instance. ### Get health Get health of Meilisearch server. #### Example ```bash curl \ -X GET 'MEILISEARCH_URL/health' ``` ```js client.health() ``` ```py client.health() ``` ```php $client->health(); ``` ```java client.health(); ``` ```ruby client.health ``` ```go client.Health() ``` ```csharp await client.HealthAsync(); ``` ```rust // health() return an Err() if the server is not healthy, so this example would panic due to the unwrap client .health() .await .unwrap(); ``` ```swift client.health { (result) in switch result { case .success: print("Healthy!") case .failure(let error): print(error) } } ``` ```dart await client.health(); ``` ##### Response: `200 OK` ```json { "status": "available" } ``` --- title: Version — Meilisearch API reference description: The /version route allows you to check the version of a running Meilisearch instance. --- ## Version The `/version` route allows you to check the version of a running Meilisearch instance. ### Version object | Name | Description | | :--------------- | :----------------------------------------------------- | | **`commitSha`** | Commit identifier that tagged the `pkgVersion` release | | **`commitDate`** | Date when the `commitSha` was created | | **`pkgVersion`** | Meilisearch version | ### Get version of Meilisearch Get version of Meilisearch. #### Example ```bash curl \ -X GET 'MEILISEARCH_URL/version' ``` ```js client.getVersion() ``` ```py client.get_version() ``` ```php $client->version(); ``` ```java client.getVersion(); ``` ```ruby client.version ``` ```go client.GetVersion() ``` ```csharp await client.GetVersionAsync(); ``` ```rust let version: Version = client .get_version() .await .unwrap(); ``` ```swift client.version { (result) in switch result { case .success(let version): print(version) case .failure(let error): print(error) } } ``` ```dart await client.getVersion(); ``` ##### Response: `200 Ok` ```json { "commitSha": "b46889b5f0f2f8b91438a08a358ba8f05fc09fc1", "commitDate": "2019-11-15T09:51:54.278247+00:00", "pkgVersion": "0.1.1" } ``` --- title: Dumps — Meilisearch API reference description: The /dumps route allows the creation of database dumps. Use dumps to migrate Meilisearch to a new version. --- ## Dumps The `/dumps` route allows the creation of database dumps. Dumps are `.dump` files that can be used to restore Meilisearch data or migrate between different versions. Meilisearch Cloud does not support the `/dumps` route. [Learn more about dumps](/learn/advanced/dumps). ### Create a dump Triggers a dump creation task. Once the process is complete, a dump is created in the [dump directory](/learn/self_hosted/configure_meilisearch_at_launch#dump-directory). If the dump directory does not exist yet, it will be created. Dump tasks take priority over all other tasks in the queue. This means that a newly created dump task will be processed as soon as the current task is finished. [Learn more about asynchronous operations](/learn/async/asynchronous_operations). #### Example ```bash curl \ -X POST 'MEILISEARCH_URL/dumps' ``` ```js client.createDump() ``` ```py client.create_dump() ``` ```php $client->createDump(); ``` ```java client.createDump(); ``` ```ruby client.create_dump ``` ```go resp, err := client.CreateDump() ``` ```csharp await client.CreateDumpAsync(); ``` ```rust client .create_dump() .await .unwrap(); ``` ```swift client.createDump { result in switch result { case .success(let dumpStatus): print(dumpStatus) case .failure(let error): print(error) } } ``` ```dart await client.createDump(); ``` ##### Response: `202 Accepted` ```json { "taskUid": 1, "indexUid": null, "status": "enqueued", "type": "dumpCreation", "enqueuedAt": "2022-06-21T16:10:29.217688Z" } ``` You can use this `taskUid` to get more details on [the status of the task](/reference/api/tasks#get-one-task) --- title: Experimental — Meilisearch API reference description: The /experimental-features route allows you to manage some of Meilisearch's experimental features. --- ## Experimental The `/experimental-features` route allows you to activate or deactivate some of Meilisearch's [experimental features](/learn/resources/experimental_features_overview). This route is **synchronous**. This means that no task object will be returned, and any activated or deactivated features will be made available or unavailable immediately. The experimental API route is not compatible with all experimental features. Consult the [experimental feature overview](/learn/resources/experimental_features_overview) for a compatibility list. ### Experimental features object ```json { "metrics": false, "logsRoute": true, "vectorStore": false, } ``` | Name | Type | Description | | :---------------------------- | :------ | :--------------------------------------------- | | **`metrics`** | Boolean | `true` if feature is active, `false` otherwise | | **`logsRoute`** | Boolean | `true` if feature is active, `false` otherwise | | **`vectorStore`** | Boolean | `true` if feature is active, `false` otherwise | | **`containsFilter`** | Boolean | `true` if feature is active, `false` otherwise | | **`editDocumentsByFunction`** | Boolean | `true` if feature is active, `false` otherwise | ### Get all experimental features Get a list of all experimental features that can be activated via the `/experimental-features` route and whether or not they are currently activated. #### Example ```bash curl \ -X GET 'MEILISEARCH_URL/experimental-features/' ``` ```go client.ExperimentalFeatures().Get() ``` ```rust let client = Client::new("http://localhost:7700", Some("apiKey")); let features = ExperimentalFeatures::new(&client); let res = features .get() .await .unwrap(); ``` ##### Response: `200 Ok` ```json { "metrics": false, "logsRoute": true, "vectorSearch": false, } ``` ### Configure experimental features Activate or deactivate experimental features. ```bash curl \ -X PATCH 'MEILISEARCH_URL/experimental-features/' \ -H 'Content-Type: application/json' \ --data-binary '{ "metrics": true }' ``` ```go client.ExperimentalFeatures().SetMetrics(true).Update() ``` ```rust let client = Client::new("http://localhost:7700", Some("apiKey")); let mut features = ExperimentalFeatures::new(&client); features.set_vector_store(true); let res = features .update() .await .unwrap(); ``` Setting a field to `null` leaves its value unchanged. ##### Body ``` {: } ``` ##### Response: `200 Ok` ```json { "metrics": false, "logsRoute": true, "vectorSearch": false, } ``` --- title: Metrics — Meilisearch API reference description: The /metrics endpoint is an experimental feature. It exposes data compatible with Prometheus and offers insight into Meilisearch's behavior and performance. --- ## Metrics The `/metrics` route exposes data compatible with [Prometheus](https://prometheus.io/). You will also need to have Grafana installed in your system to make use of this feature. 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 '{ "metrics": true }' ``` This feature is not available for Meilisearch Cloud users. ### Exposed information `/metrics` exposes the following information: | Name | Description | Type | |------------------------------------------|-------------|-----------| | `meilisearch_http_requests_total` | Returns the number of times an API resource is accessed. | counter | | `meilisearch_http_response_time_seconds` | Returns a time histogram showing the number of times an API resource call goes into a time bucket (expressed in second). | histogram | | `meilisearch_db_size_bytes` | Returns the “real” size of the database on disk in bytes. It includes all the lmdb memory mapped files plus all the files contained in the `data.ms` directory (mainly the updates files that were not processed yet). | gauge | | `meilisearch_used_db_size_bytes` | Returns the size of the database actually used by meilisearch in bytes. Include all the same files as `meilisearch_db_size_bytes` except that when it comes to an LMDB database, we only count the pages used by meilisearch. This means if you see a large gap between both metrics, adding documents will probably re-use freed pages instead of growing `meilisearch_db_size_bytes`. | gauge | | `meilisearch_index_docs_count` | Returns the number of documents for an index. | gauge | | `meilisearch_index_count` | Returns the total number of index for the Meilisearch instance. | gauge | | `meilisearch_nb_tasks` | Returns the total number of tasks for the Meilisearch instance parametrized by the kind of task and its value (see the table below). | counter | | `meilisearch_last_update` | Returns the timestamp of the last update. | gauge | | `meilisearch_is_indexing` | Returns `1` if Meilisearch is indexing or `0` if not. | gauge | API keys with access to `/metrics` are able to see all HTTP calls for all the routes in an instance. This may lead to leaking sensitive information such as index names, document's primary keys, and API keys. ### Get metrics Get data for current status of your instance. In most cases, you should only query this endpoint via a Prometheus-compatible tool such as Grafana. Refer to Meilisearch's sample configuration files for example of a [basic Prometheus scraper](https://github.com/orgs/meilisearch/discussions/assets/prometheus-basic-scraper.yml) and [Grafana dashboard](https://github.com/meilisearch/meilisearch/blob/main/assets/grafana-dashboard.json). #### Example ```bash curl \ -X GET 'MEILISEARCH_URL/metrics' ``` ##### Response: `200 OK` ``` ## HELP meilisearch_db_size_bytes Meilisearch DB Size In Bytes ## TYPE meilisearch_db_size_bytes gauge meilisearch_db_size_bytes 188416 ## HELP meilisearch_http_response_time_seconds Meilisearch HTTP response times ## TYPE meilisearch_http_response_time_seconds histogram meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.005"} 0 meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.01"} 0 meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.025"} 0 meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.05"} 0 meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.075"} 0 meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.1"} 0 meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.25"} 0 meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.5"} 0 meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.75"} 0 meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="1"} 0 meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="2.5"} 0 meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="5"} 0 meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="7.5"} 0 meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="10"} 0 meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="+Inf"} 0 meilisearch_http_response_time_seconds_sum{method="GET",path="/metrics"} 0 meilisearch_http_response_time_seconds_count{method="GET",path="/metrics"} 0 ## HELP meilisearch_index_count Meilisearch Index Count ## TYPE meilisearch_index_count gauge meilisearch_index_count 1 ## HELP meilisearch_index_docs_count Meilisearch Index Docs Count ## TYPE meilisearch_index_docs_count gauge meilisearch_index_docs_count{index="books"} 6 ## HELP meilisearch_is_indexing Meilisearch Is Indexing ## TYPE meilisearch_is_indexing gauge meilisearch_is_indexing 0 ## HELP meilisearch_last_update Meilisearch Last Update ## TYPE meilisearch_last_update gauge meilisearch_last_update 1723126669 ## HELP meilisearch_nb_tasks Meilisearch Number of tasks ## TYPE meilisearch_nb_tasks gauge meilisearch_nb_tasks{kind="indexes",value="books"} 1 meilisearch_nb_tasks{kind="statuses",value="canceled"} 0 meilisearch_nb_tasks{kind="statuses",value="enqueued"} 0 meilisearch_nb_tasks{kind="statuses",value="failed"} 0 meilisearch_nb_tasks{kind="statuses",value="processing"} 0 meilisearch_nb_tasks{kind="statuses",value="succeeded"} 1 meilisearch_nb_tasks{kind="types",value="documentAdditionOrUpdate"} 1 meilisearch_nb_tasks{kind="types",value="documentDeletion"} 0 meilisearch_nb_tasks{kind="types",value="documentEdition"} 0 meilisearch_nb_tasks{kind="types",value="dumpCreation"} 0 meilisearch_nb_tasks{kind="types",value="indexCreation"} 0 meilisearch_nb_tasks{kind="types",value="indexDeletion"} 0 meilisearch_nb_tasks{kind="types",value="indexSwap"} 0 meilisearch_nb_tasks{kind="types",value="indexUpdate"} 0 meilisearch_nb_tasks{kind="types",value="settingsUpdate"} 0 meilisearch_nb_tasks{kind="types",value="snapshotCreation"} 0 meilisearch_nb_tasks{kind="types",value="taskCancelation"} 0 meilisearch_nb_tasks{kind="types",value="taskDeletion"} 0 ## HELP meilisearch_used_db_size_bytes Meilisearch Used DB Size In Bytes ## TYPE meilisearch_used_db_size_bytes gauge meilisearch_used_db_size_bytes 90112 ``` --- title: Log customization — Meilisearch documentation description: "Customize Meilisearch logs with two experimental features: --experimental-logs-mode and --experimental-enable-logs-route." --- ## Logs 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 '{ "logsRoute": true }' ``` This feature is not available for Meilisearch Cloud users. ### Customize log levels Customize logging levels for the default logging system. #### Body | Name | Type | Default value | Description | | :-------------------------------- | :----- | :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------- | | **`target`** * | String | N/A | A string specifying one or more log type and its log level | #### Example ```bash curl \ -X POST MEILISEARCH_URL/logs/stderr \ -H 'Content-Type: application/json' \ --data-binary '{ "target": "milli=trace,index_scheduler=info,actix_web=off" }' ``` ### Start log stream Opens a continuous stream of logs for focused debugging sessions. The stream will continue to run indefinitely until you [interrupt](#interrupt-log-stream) it. #### Body | Name | Type | Default value | Description | | :-------------------------------- | :----- | :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------- | | **`mode`** * | String | N/A | Specifies either human-readabale or JSON output | | **`target`** * | String | N/A | A string specifying one or more log type and its log level | ### Example ```bash curl \ -X POST MEILISEARCH_URL/logs/stream \ -H 'Content-Type: application/json' \ --data-binary '{ "mode": "human", "target": "index_scheduler=trace" }' ``` Certain HTTP clients such as `httpie` and `xh`, will only display data after you have interrupted the stream with the `DELETE` endpoint. ### Interrupt log stream Interrupt a log stream. ### Example ```bash curl \ -X DELETE MEILISEARCH_URL/logs/stream ``` --- title: Errors — Meilisearch API reference description: Consult this page for an overview of how Meilisearch reports and formats error objects. --- ## Status codes and Meilisearch errors Meilisearch uses the following standard HTTP codes for a successful or failed API request: | Status code | Description | | :---------- | :----------------------------------------------------------------------------------------- | | 200 | ✅ **Ok** Everything worked as expected. | | 201 | ✅ **Created** The resource has been created (synchronous) | | 202 | ✅ **Accepted** The task has been added to the queue (asynchronous) | | 204 | ✅ **No Content** The resource has been deleted or no content has been returned | | 205 | ✅ **Reset Content** All the resources have been deleted | | 400 | ❌ **Bad Request** The request was unacceptable, often due to missing a required parameter | | 401 | ❌ **Unauthorized** No valid API key provided | | 403 | ❌ **Forbidden** The API key doesn't have the permissions to perform the request | | 404 | ❌ **Not Found** The requested resource doesn't exist | ### Errors All detailed task responses contain an [`error`](/reference/api/tasks#error) field. When a task fails, it is always accompanied by a JSON-formatted error response. Meilisearch errors can be of one of the following types: | Type | Description | | :-------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`invalid_request`** | This is due to an error in the user input. It is accompanied by the HTTP code `4xx` | | **`internal`** | This is due to machine or configuration constraints. It is accompanied by the HTTP code `5xx` | | **`auth`** | This type of error is related to authentication and authorization. It is accompanied by the HTTP code `4xx` | | **`system`** | This indicates your system has reached or exceeded its limit for disk size, index size, open files, or the database doesn't have read or write access. It is accompanied by the HTTP code `5xx` | #### Error format ```json { "message": "Index `movies` not found.", "code": "index_not_found", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#index_not_found" } ``` | Field | Description | | :------------ | :------------------------------------------------ | | **`message`** | Human-readable description of the error | | **`code`** | [Error code](/reference/errors/error_codes) | | **`type`** | [Type](#errors) of error returned | | **`link`** | Link to the relevant section of the documentation | If you're having trouble understanding an error, take a look at the [complete list](/reference/errors/error_codes) of `code` values and descriptions. --- title: Error codes — Meilisearch API reference description: Consult this page for an exhaustive list of errors you may encounter when using the Meilisearch API. --- ## Error codes This page is an exhaustive list of Meilisearch API errors. ### `api_key_already_exists` A key with this [`uid`](/reference/api/keys#uid) already exists. ### `api_key_not_found` The requested API key could not be found. ### `bad_request` The request is invalid, check the error message for more information. ### `batch_not_found` The requested batch does not exist. Please ensure that you are using the correct [`uid`](/reference/api/batches#uid). ### `database_size_limit_reached` The requested database has reached its maximum size. ### `document_fields_limit_reached` A document exceeds the [maximum limit of 65,535 fields](/learn/resources/known_limitations#maximum-number-of-attributes-per-document). ### `document_not_found` The requested document can't be retrieved. Either it doesn't exist, or the database was left in an inconsistent state. ### `dump_process_failed` An error occurred during the dump creation process. The task was aborted. ### `facet_search_disabled` The [`/facet-search`](/reference/api/facet_search) route has been queried while [the `facetSearch` index setting](/reference/api/settings#facet-search) is set to `false`. ### `immutable_api_key_actions` The [`actions`](/reference/api/keys#actions) field of an API key cannot be modified. ### `immutable_api_key_created_at` The [`createdAt`](/reference/api/keys#createdat) field of an API key cannot be modified. ### `immutable_api_key_expires_at` The [`expiresAt`](/reference/api/keys#expiresat) field of an API key cannot be modified. ### `immutable_api_key_indexes` The [`indexes`](/reference/api/keys#indexes) field of an API key cannot be modified. ### `immutable_api_key_key` The [`key`](/reference/api/keys#key) field of an API key cannot be modified. ### `immutable_api_key_uid` The [`uid`](/reference/api/keys#uid) field of an API key cannot be modified. ### `immutable_api_key_updated_at` The [`updatedAt`](/reference/api/keys#updatedat) field of an API key cannot be modified. ### `immutable_index_uid` The [`uid`](/reference/api/indexes#index-object) field of an index cannot be modified. ### `immutable_index_updated_at` The [`updatedAt`](/reference/api/indexes#index-object) field of an index cannot be modified. ### `index_already_exists` An index with this [`uid`](/reference/api/indexes#index-object) already exists, check out our guide on [index creation](/learn/getting_started/indexes). ### `index_creation_failed` An error occurred while trying to create an index, check out our guide on [index creation](/learn/getting_started/indexes). ### `index_not_found` An index with this `uid` was not found, check out our guide on [index creation](/learn/getting_started/indexes). ### `index_primary_key_already_exists` The requested index already has a primary key that [cannot be changed](/learn/getting_started/primary_key#changing-your-primary-key-with-the-update-index-endpoint). ### `index_primary_key_multiple_candidates_found` [Primary key inference](/learn/getting_started/primary_key#meilisearch-guesses-your-primary-key) failed because the received documents contain multiple fields ending with `id`. Use the [update index endpoint](/reference/api/indexes#update-an-index) to manually set a primary key. ### `internal` Meilisearch experienced an internal error. Check the error message, and [open an issue](https://github.com/meilisearch/meilisearch/issues/new?assignees=&labels=&template=bug_report&title=) if necessary. ### `invalid_api_key` The requested resources are protected with an API key. The provided API key is invalid. Read more about it in our [security tutorial](/learn/security/basic_security). ### `invalid_api_key_actions` The [`actions`](/reference/api/keys#actions) field for the provided API key resource is invalid. It should be an array of strings representing action names. ### `invalid_api_key_description` The [`description`](/reference/api/keys#description) field for the provided API key resource is invalid. It should either be a string or set to `null`. ### `invalid_api_key_expires_at` The [`expiresAt`](/reference/api/keys#expiresat) field for the provided API key resource is invalid. It should either show a future date or datetime in the [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format or be set to `null`. ### `invalid_api_key_indexes` The [`indexes`](/reference/api/keys#indexes) field for the provided API key resource is invalid. It should be an array of strings representing index names. ### `invalid_api_key_limit` The [`limit`](/reference/api/keys#query-parameters) parameter is invalid. It should be an integer. ### `invalid_api_key_name` The given [`name`](/reference/api/keys#name) is invalid. It should either be a string or set to `null`. ### `invalid_api_key_offset` The [`offset`](/reference/api/keys#query-parameters) parameter is invalid. It should be an integer. ### `invalid_api_key_uid` The given [`uid`](/reference/api/keys#uid) is invalid. The `uid` must follow the [uuid v4](https://www.sohamkamani.com/uuid-versions-explained) format. ### `invalid_search_attributes_to_search_on` The value passed to [`attributesToSearchOn`](/reference/api/search#customize-attributes-to-search-on-at-search-time) is invalid. `attributesToSearchOn` accepts an array of strings indicating document attributes. Attributes given to `attributesToSearchOn` must be present in the [`searchableAttributes` list](/learn/relevancy/displayed_searchable_attributes#the-searchableattributes-list). ### `invalid_content_type` The [Content-Type header](/reference/api/overview#content-type) is not supported by Meilisearch. Currently, Meilisearch only supports JSON, CSV, and NDJSON. ### `invalid_document_csv_delimiter` The [`csvDelimiter`](/reference/api/documents#add-or-replace-documents) parameter is invalid. It should either be a string or [a single ASCII character](https://www.rfc-editor.org/rfc/rfc20). ### `invalid_document_id` The provided [document identifier](/learn/getting_started/primary_key#document-id) does not meet the format requirements. A document identifier must be of type integer or string, composed only of alphanumeric characters (a-z A-Z 0-9), hyphens (-), and underscores (_). ### `invalid_document_fields` The [`fields`](/reference/api/documents#query-parameters) parameter is invalid. It should be a string. ### `invalid_document_filter` This error occurs if: - The [`filter`](/reference/api/documents#query-parameters) parameter is invalid - It should be a string, array of strings, or array of array of strings for the [get documents with POST endpoint](/reference/api/documents#get-documents-with-post) - It should be a string for the [get documents with GET endpoint](/reference/api/documents#get-documents-with-get) - The attribute used for filtering is not defined in the [`filterableAttributes` list](/reference/api/settings#filterable-attributes) - The [filter expression](/learn/filtering_and_sorting/filter_expression_reference) has a missing or invalid operator. [Read more about our supported operators](/learn/filtering_and_sorting/filter_expression_reference) ### `invalid_document_limit` The [`limit`](/reference/api/documents#query-parameters) parameter is invalid. It should be an integer. ### `invalid_document_offset` The [`offset`](/reference/api/documents#query-parameters) parameter is invalid. It should be an integer. ### `invalid_document_geo_field` The provided `_geo` field of one or more documents is invalid. Meilisearch expects `_geo` to be an object with two fields, `lat` and `lng`, each containing geographic coordinates expressed as a string or floating point number. Read more about `_geo` and how to troubleshoot it in [our dedicated guide](/learn/filtering_and_sorting/geosearch). ### `invalid_facet_search_facet_name` The attribute used for the `facetName` field is either not a string or not defined in the [`filterableAttributes` list](/reference/api/settings#filterable-attributes). ### `invalid_facet_search_facet_query` The provided value for `facetQuery` is invalid. It should either be a string or `null`. ### `invalid_index_limit` The [`limit`](/reference/api/indexes#query-parameters) parameter is invalid. It should be an integer. ### `invalid_index_offset` The [`offset`](/reference/api/indexes#query-parameters) parameter is invalid. It should be an integer. ### `invalid_index_uid` There is an error in the provided index format, check out our guide on [index creation](/learn/getting_started/indexes). ### `invalid_index_primary_key` The [`primaryKey`](/reference/api/indexes#body-2) field is invalid. It should either be a string or set to `null`. ### `invalid_multi_search_query_federated` A multi-search query includes `federationOptions` but the top-level `federation` object is `null` or missing. ### `invalid_multi_search_query_pagination` A multi-search query contains `page`, `hitsPerPage`, `limit` or `offset`, but the top-level federation object is not `null`. ### `invalid_multi_search_weight` A multi-search query contains a negative value for `federated.weight`. ### `invalid_multi_search_queries_ranking_rules` Two or more queries in a multi-search request have incompatible results. ### `invalid_multi_search_facets` `federation.facetsByIndex.` contains a value that is not in the filterable attributes list. ### `invalid_multi_search_sort_facet_values_by` `federation.mergeFacets.sortFacetValuesBy` is not a string or doesn’t have one of the allowed values. ### `invalid_multi_search_query_facets` A query in the queries array contains `facets` when federation is present and non-`null`. ### `invalid_multi_search_merge_facets` `federation.mergeFacets` is not an object or contains unexpected fields. ### `invalid_multi_search_max_values_per_facet` `federation.mergeFacets.maxValuesPerFacet` is not a positive integer. ### `invalid_multi_search_facet_order` Two or more indexes have a different `faceting.sortFacetValuesBy` for the same requested facet. ### `invalid_multi_search_facets_by_index` `facetsByIndex` is not an object or contains unknown fields. ### `invalid_search_attributes_to_crop` The [`attributesToCrop`](/reference/api/search#attributes-to-crop) parameter is invalid. It should be an array of strings, a string, or set to `null`. ### `invalid_search_attributes_to_highlight` The [`attributesToHighlight`](/reference/api/search#attributes-to-highlight) parameter is invalid. It should be an array of strings, a string, or set to `null`. ### `invalid_search_attributes_to_retrieve` The [`attributesToRetrieve`](/reference/api/search#attributes-to-retrieve) parameter is invalid. It should be an array of strings, a string, or set to `null`. ### `invalid_search_crop_length` The [`cropLength`](/reference/api/search#crop-length) parameter is invalid. It should be an integer. ### `invalid_search_crop_marker` The [`cropMarker`](/reference/api/search#crop-marker) parameter is invalid. It should be a string or set to `null`. ### `invalid_search_facets` This error occurs if: - The [`facets`](/reference/api/search#facets) parameter is invalid. It should be an array of strings, a string, or set to `null` - The attribute used for faceting is not defined in the [`filterableAttributes` list](/reference/api/settings#filterable-attributes) ### `invalid_search_filter` This error occurs if: - The syntax for the [`filter`](/reference/api/search#filter) parameter is invalid - The attribute used for filtering is not defined in the [`filterableAttributes` list](/reference/api/settings#filterable-attributes) - A reserved keyword like `_geo`, `_geoDistance`, or `_geoPoint` is used as a filter ### `invalid_search_highlight_post_tag` The [`highlightPostTag`](/reference/api/search#highlight-tags) parameter is invalid. It should be a string. ### `invalid_search_highlight_pre_tag` The [`highlightPreTag`](/reference/api/search#highlight-tags) parameter is invalid. It should be a string. ### `invalid_search_hits_per_page` The [`hitsPerPage`](/reference/api/search#number-of-results-per-page) parameter is invalid. It should be an integer. ### `invalid_search_limit` The [`limit`](/reference/api/search#limit) parameter is invalid. It should be an integer. ### `invalid_search_locales` The [`locales`](/reference/api/search#query-locales) parameter is invalid. ### `invalid_settings_facet_search` The [`facetSearch`](/reference/api/settings#facet-search) index setting value is invalid. ### `invalid_settings_localized_attributes` The [`localizedAttributes`](/reference/api/settings#localized-attributes) index setting value is invalid. ### `invalid_search_matching_strategy` The [`matchingStrategy`](/reference/api/search#matching-strategy) parameter is invalid. It should either be set to `last` or `all`. ### `invalid_search_offset` The [`offset`](/reference/api/search#offset) parameter is invalid. It should be an integer. ### `invalid_settings_prefix_search` The [`prefixSearch`](/reference/api/settings#prefix-search) index setting value is invalid. ### `invalid_search_page` The [`page`](/reference/api/search#page) parameter is invalid. It should be an integer. ### `invalid_search_q` The [`q`](/reference/api/search#query-q) parameter is invalid. It should be a string or set to `null` ### `invalid_search_ranking_score_threshold` The [`rankingScoreThreshold`](/reference/api/search#ranking-score-threshold) in a search or multi-search request is not a number between `0.0` and `1.0`. ### `invalid_search_show_matches_position` The [`showMatchesPosition`](/reference/api/search#show-matches-position) parameter is invalid. It should either be a boolean or set to `null`. ### `invalid_search_sort` This error occurs if: - The syntax for the [`sort`](/reference/api/search#sort) parameter is invalid - The attribute used for sorting is not defined in the [`sortableAttributes`](/reference/api/settings#sortable-attributes) list or the `sort` ranking rule is missing from the settings - A reserved keyword like `_geo`, `_geoDistance`, `_geoRadius`, or `_geoBoundingBox` is used as a filter ### `invalid_settings_displayed_attributes` The value of [displayed attributes](/learn/relevancy/displayed_searchable_attributes#displayed-fields) is invalid. It should be an empty array, an array of strings, or set to `null`. ### `invalid_settings_distinct_attribute` The value of [distinct attributes](/learn/relevancy/distinct_attribute) is invalid. It should be a string or set to `null`. ### `invalid_settings_faceting_sort_facet_values_by` The value provided for the [`sortFacetValuesBy`](/reference/api/settings#faceting-object) object is incorrect. The accepted values are `alpha` or `count`. ### `invalid_settings_faceting_max_values_per_facet` The value for the [`maxValuesPerFacet`](/reference/api/settings#faceting-object) field is invalid. It should either be an integer or set to `null`. ### `invalid_settings_filterable_attributes` The value of [filterable attributes](/reference/api/settings#filterable-attributes) is invalid. It should be an empty array, an array of strings, or set to `null`. ### `invalid_settings_pagination` The value for the [`maxTotalHits`](/reference/api/settings#pagination-object) field is invalid. It should either be an integer or set to `null`. ### `invalid_settings_ranking_rules` This error occurs if: - The [settings payload](/reference/api/settings#body) has an invalid format - A non-existent ranking rule is specified - A custom ranking rule is malformed - A reserved keyword like `_geo`, `_geoDistance`, `_geoRadius`, `_geoBoundingBox`, or `_geoPoint` is used as a custom ranking rule ### `invalid_settings_searchable_attributes` The value of [searchable attributes](/reference/api/settings#searchable-attributes) is invalid. It should be an empty array, an array of strings or set to `null`. ### `invalid_settings_search_cutoff_ms` The specified value for [`searchCutoffMs](/reference/api/settings#search-cutoff) is invalid. It should be an integer indicating the cutoff in milliseconds. ### `invalid_settings_sortable_attributes` The value of [sortable attributes](/reference/api/settings#sortable-attributes) is invalid. It should be an empty array, an array of strings or set to `null`. ### `invalid_settings_stop_words` The value of [stop words](/reference/api/settings#stop-words) is invalid. It should be an empty array, an array of strings or set to `null`. ### `invalid_settings_synonyms` The value of the [synonyms](/reference/api/settings#synonyms) is invalid. It should either be an object or set to `null`. ### `invalid_settings_typo_tolerance` This error occurs if: - The [`enabled`](/reference/api/settings#typo-tolerance-object) field is invalid. It should either be a boolean or set to `null` - The [`disableOnAttributes`](/reference/api/settings#typo-tolerance-object) field is invalid. It should either be an array of strings or set to `null` - The [`disableOnWords`](/reference/api/settings#typo-tolerance-object) field is invalid. It should either be an array of strings or set to `null` - The [`minWordSizeForTypos`](/reference/api/settings#typo-tolerance-object) field is invalid. It should either be an integer or set to `null` - The value of either [`oneTypo`](/reference/api/settings#typo-tolerance-object) or [`twoTypos`](/reference/api/settings#typo-tolerance-object) is invalid. It should either be an integer or set to `null` #### `invalid_similar_id` The provided target document identifier is invalid. A document identifier can be of type integer or string, only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and underscores (_). #### `not_found_similar_id` Meilisearch could not find the target document. Make sure your target document identifier corresponds to a document in your index. #### `invalid_similar_attributes_to_retrieve` [`attributesToRetrieve`](/reference/api/search#attributes-to-retrieve) is invalid. It should be an array of strings, a string, or set to null. #### `invalid_similar_filter` [`filter`](/reference/api/search#filter) is invalid or contains a filter expression with a missing or invalid operator. Filter expressions must be a string, array of strings, or array of array of strings for the POST endpoint. It must be a string for the GET endpoint. Meilisearch also throws this error if the attribute used for filtering is not defined in the `filterableAttributes` list. #### `invalid_similar_limit` [`limit`](/reference/api/search#limit) is invalid. It should be an integer. #### `invalid_similar_offset` [`offset`](/reference/api/search#offset) is invalid. It should be an integer. #### `invalid_similar_show_ranking_score` [`ranking_score`](/reference/api/search#ranking-score) is invalid. It should be a boolean. #### `invalid_similar_show_ranking_score_details` [`ranking_score_details`](/reference/api/search#ranking-score-details) is invalid. It should be a boolean. #### `invalid_embedder` [`embedder`](/reference/api/search#hybrid-search-experimental) is invalid. It should be a string corresponding to the name of a configured embedder. ### `invalid_similar_ranking_score_threshold` The [`rankingScoreThreshold`](/reference/api/search#ranking-score-threshold) in a similar documents request is not a number between `0.0` and `1.0`. ### `invalid_state` The database is in an invalid state. Deleting the database and re-indexing should solve the problem. ### `invalid_store_file` The `data.ms` folder is in an invalid state. Your `b` file is corrupted or the `data.ms` folder has been replaced by a file. ### `invalid_swap_duplicate_index_found` The indexes used in the [`indexes`](/reference/api/indexes#body-2) array for a [swap index](/reference/api/indexes#swap-indexes) request have been declared multiple times. You must declare each index only once. ### `invalid_swap_indexes` This error happens if: - The payload doesn't contain exactly two index [`uids`](/reference/api/indexes#body-2) for a swap operation - The payload contains an invalid index name in the [`indexes`](/reference/api/indexes#body-2) array ### `invalid_task_after_enqueued_at` The [`afterEnqueuedAt`](/reference/api/tasks#query-parameters) query parameter is invalid. ### `invalid_task_after_finished_at` The [`afterFinishedAt`](/reference/api/tasks#query-parameters) query parameter is invalid. ### `invalid_task_after_started_at` The [`afterStartedAt`](/reference/api/tasks#query-parameters) query parameter is invalid. ### `invalid_task_before_enqueued_at` The [`beforeEnqueuedAt`](/reference/api/tasks#query-parameters) query parameter is invalid. ### `invalid_task_before_finished_at` The [`beforeFinishedAt`](/reference/api/tasks#query-parameters) query parameter is invalid. ### `invalid_task_before_started_at` The [`beforeStartedAt`](/reference/api/tasks#query-parameters) query parameter is invalid. ### `invalid_task_canceled_by` The [`canceledBy`](/reference/api/tasks#canceledby) query parameter is invalid. It should be an integer. Multiple `uid`s should be separated by commas (`,`). ### `invalid_task_index_uids` The [`indexUids`](/reference/api/tasks#query-parameters) query parameter contains an invalid index uid. ### `invalid_task_limit` The [`limit`](/reference/api/tasks#query-parameters) parameter is invalid. It must be an integer. ### `invalid_task_statuses` The requested task status is invalid. Please use one of the [possible values](/reference/api/tasks#status). ### `invalid_task_types` The requested task type is invalid. Please use one of the [possible values](/reference/api/tasks#type). ### `invalid_task_uids` The [`uids`](/reference/api/tasks#query-parameters) query parameter is invalid. ### `io_error` This error generally occurs when the host system has no space left on the device or when the database doesn't have read or write access. ### `index_primary_key_no_candidate_found` [Primary key inference](/learn/getting_started/primary_key#meilisearch-guesses-your-primary-key) failed as the received documents do not contain any fields ending with `id`. [Manually designate the primary key](/learn/getting_started/primary_key#setting-the-primary-key), or add some field ending with `id` to your documents. ### `malformed_payload` The [Content-Type header](/reference/api/overview#content-type) does not match the request body payload format or the format is invalid. ### `missing_api_key_actions` The [`actions`](/reference/api/keys#actions) field is missing from payload. ### `missing_api_key_expires_at` The [`expiresAt`](/reference/api/keys#expiresat) field is missing from payload. ### `missing_api_key_indexes` The [`indexes`](/reference/api/keys#indexes) field is missing from payload. ### `missing_authorization_header` This error happens if: - The requested resources are protected with an API key that was not provided in the request header. Check our [security tutorial](/learn/security/basic_security) for more information - You are using the wrong authorization header for your version. **v0.24 and below** use `X-MEILI-API-KEY: apiKey`, whereas **v0.25 and above** use `Authorization: Bearer apiKey` ### `missing_content_type` The payload does not contain a [Content-Type header](/reference/api/overview#content-type). Currently, Meilisearch only supports JSON, CSV, and NDJSON. ### `missing_document_filter` This payload is missing the [`filter`](/reference/api/documents#body-3) field. ### `missing_document_id` A document does not contain any value for the required primary key, and is thus invalid. Check documents in the current addition for the invalid ones. ### `missing_index_uid` The payload is missing the [`uid`](/reference/api/indexes#index-object) field. ### `missing_facet_search_facet_name` The [`facetName`](/reference/api/facet_search#body) parameter is required. ### `missing_master_key` You need to set a master key before you can access the `/keys` route. Read more about setting a master key at launch in our [security tutorial](/learn/security/basic_security). ### `missing_payload` The Content-Type header was specified, but no request body was sent to the server or the request body is empty. ### `missing_swap_indexes` The index swap payload is missing the [`indexes`](/reference/api/indexes#swap-indexes) object. ### `missing_task_filters` The [cancel tasks](/reference/api/tasks#cancel-tasks) and [delete tasks](/reference/api/tasks#delete-tasks) endpoints require one of the available query parameters. ### `no_space_left_on_device` This error occurs if: - The host system partition reaches its maximum capacity and can no longer accept writes - The tasks queue reaches its limit and can no longer accept writes. You can delete tasks using the [delete tasks endpoint](/reference/api/tasks#delete-tasks) to continue write operations ### `not_found` The requested resources could not be found. ### `payload_too_large` The payload sent to the server was too large. Check out this [guide](/learn/self_hosted/configure_meilisearch_at_launch#payload-limit-size) to customize the maximum payload size accepted by Meilisearch. ### `task_not_found` The requested task does not exist. Please ensure that you are using the correct [`uid`](/reference/api/tasks#uid). ### `too_many_open_files` Indexing a large batch of documents, such as a JSON file over 3.5GB in size, can result in Meilisearch opening too many file descriptors. Depending on your machine, this might reach your system's default resource usage limits and trigger the `too_many_open_files` error. Use [`ulimit`](https://www.ibm.com/docs/en/aix/7.1?topic=u-ulimit-command) or a similar tool to increase resource consumption limits before running Meilisearch. For example, call `ulimit -Sn 3000` in a UNIX environment to raise the number of allowed open file descriptors to 3000. ### `too_many_search_requests` You have reached the limit of concurrent search requests. You may configure it by relaunching your instance and setting a higher value to [`--experimental-search-queue-size`](/learn/self_hosted/configure_meilisearch_at_launch). ### `unretrievable_document` The document exists in store, but there was an error retrieving it. This probably comes from an inconsistent state in the database. #### `vector_embedding_error` Error while generating embeddings. --- title: Front-end integration — Meilisearch documentation description: Create a simple front-end interface to search through your dataset after following Meilisearch's quick start. --- ## Front-end integration In the [quick start tutorial](/learn/self_hosted/getting_started_with_self_hosted_meilisearch), you learned how to launch Meilisearch and make a search request. This article will teach you how to create a simple front-end interface to search through your dataset. Using [`instant-meilisearch`](https://github.com/meilisearch/instant-meilisearch) is the easiest way to build a front-end interface for search. `instant-meilisearch` is a plugin that establishes communication between a Meilisearch instance and [InstantSearch](https://github.com/algolia/instantsearch.js). InstantSearch, an open-source project developed by Algolia, renders all the components needed to start searching. ### Let's try it! 1. Create an empty file and name it `index.html` 2. Open it in a text editor like Notepad, Sublime Text, or Visual Studio Code 3. Copy-paste one of the code samples above—either vanilla JavaScript, Vue 2, or React— and save the file 4. Open `index.html` in your browser by double-clicking it in your folder ```js The following code sample uses plain [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript). ```html
``` Here's what's happening: - The first four lines of the `` add two container elements: `#searchbox` and `#hits`. `instant-meilisearch` creates the search bar inside `#searchbox` and lists search results in `#hits` - The first two` ``` Here's what's happening: - The `< div id="app">` inside `` is React's entry point. `instant-meilisearch` creates the search bar and the search result container inside this HTML element by manipulating the DOM - The first four` ``` Here's what's happening: - To use `instant-meilisearch` with Vue, you must add ``, ``, and `` to your application's HTML. These components are mandatory when generating the`instant-meilisearch` interface - Other Vue components such as `` and `` are optional. They offer greater control over `instant-meilisearch`'s behavior and appearance - The first two` ``` Here's what's happening: - To use `instant-meilisearch` with Vue, you must add ``, ``, and `` to your application's HTML. These components are mandatory when generating the `instant-meilisearch` interface - Other Vue components such as `` and `` are optional. They offer greater control over `instant-meilisearch`'s behavior and appearance - The three ` ``` These URL and API key point to a public Meilisearch instance that contains data from Steam video games. The `ais-instant-search` widget is the mandatory wrapper that allows you to configure your search. It takes two props: the `search-client` and the [`index-name`](/learn/getting_started/indexes#index-uid). ### 5. Add a search bar and list search results Add the `ais-search-box` and `ais-hits` widgets inside the `ais-instant-search` wrapper widget. Import the CSS library to style the search components. ``` ``` Use the slot directive to customize how each search result is rendered. Use the following CSS classes to add custom styles to your components: `.ais-InstantSearch`, `.ais-SearchBox`, `.ais-InfiniteHits-list`, `.ais-InfiniteHits-item` ### 6.Start the app and search as you type Start the app by running: ```bash npm run dev ``` Now open your browser, navigate to your Vue app URL (e.g., `localhost:5173`), and start searching. ![Vue app search UI with a search bar at the top and search results for a few video games](/assets/images/react_quick_start/react-qs-search-ui.png) Encountering issues? Check out the code in action in our [live demo](https://codesandbox.io/p/sandbox/ms-vue3-is-forked-wsrkl8)! ### Next steps Want to search through your own data? [Create a project](https://cloud.meilisearch.com) in the Meilisearch Dashboard. Check out our [getting started guide](/learn/getting_started/cloud_quick_start) for step-by-step instructions. --- title: Integrate a relevant search bar to your documentation — Meilisearch documentation description: Use Meilisearch to index content in a text-heavy website. Covers installing Meilisearch, configuring a text scraper, and creating a simple front end. --- ## Integrate a relevant search bar to your documentation This tutorial will guide you through the steps of building a relevant and powerful search bar for your documentation 🚀 1. [Run a Meilisearch Instance](#run-a-meilisearch-instance) 2. [Scrape your content](#scrape-your-content) 3. [Integrate the Search Bar](#integrate-the-search-bar) ### Run a Meilisearch instance First, create a new Meilisearch project on Meilisearch Cloud. You can also [install and run Meilisearch locally or in another cloud service](/learn/self_hosted/getting_started_with_self_hosted_meilisearch#setup-and-installation). The host URL and the API key you will provide in the next steps correspond to the credentials of this Meilisearch instance. ### Scrape your content The Meilisearch team provides and maintains a [scraper tool](https://github.com/meilisearch/docs-scraper) to automatically read the content of your website and store it into an index in Meilisearch. #### Configuration file The scraper tool needs a configuration file to know what content you want to scrape. This is done by providing selectors (for example, the `html` tag). Here is an example of a basic configuration file: ```json { "index_uid": "docs", "start_urls": [ "https://www.example.com/doc/" ], "sitemap_urls": [ "https://www.example.com/sitemap.xml" ], "stop_urls": [], "selectors": { "lvl0": { "selector": ".docs-lvl0", "global": true, "default_value": "Documentation" }, "lvl1": { "selector": ".docs-lvl1", "global": true, "default_value": "Chapter" }, "lvl2": ".docs-content .docs-lvl2", "lvl3": ".docs-content .docs-lvl3", "lvl4": ".docs-content .docs-lvl4", "lvl5": ".docs-content .docs-lvl5", "lvl6": ".docs-content .docs-lvl6", "text": ".docs-content p, .docs-content li" } } ``` The `index_uid` field is the index identifier in your Meilisearch instance in which your website content is stored. The scraping tool will create a new index if it does not exist. The `docs-content` class is the main container of the textual content in this example. Most of the time, this tag is a `
` or an `
` HTML element. `lvlX` selectors should use the standard title tags like `h1`, `h2`, `h3`, etc. You can also use static classes. Set a unique `id` or `name` attribute to these elements. All searchable `lvl` elements outside this main documentation container (for instance, in a sidebar) must be `global` selectors. They will be globally picked up and injected to every document built from your page. If you use VuePress for your documentation, you can check out the [configuration file](https://github.com/meilisearch/documentation/blob/main/docs-scraper.config.json) we use in production. In our case, the main container is `theme-default-content` and the selector titles and subtitles are `h1`, `h2`... More [optional fields are available](https://github.com/meilisearch/docs-scraper#all-the-config-file-settings) to fit your needs. #### Run the scraper You can run the scraper with Docker. With our local Meilisearch instance set up at [the first step](#run-a-meilisearch-instance), we run: ```bash docker run -t --rm \ --network=host \ -e MEILISEARCH_HOST_URL='MEILISEARCH_URL' \ -e MEILISEARCH_API_KEY='MASTER_KEY' \ -v :/docs-scraper/config.json \ getmeili/docs-scraper:latest pipenv run ./docs_scraper config.json ``` If you don't want to use Docker, here are [other ways to run the scraper](https://github.com/meilisearch/docs-scraper#installation-and-usage). `` should be the **absolute** path of your configuration file defined at [the previous step](#configuration-file). The API key should have the permissions to add documents into your Meilisearch instance. In a production environment, we recommend providing the `Default Admin API Key` as it has enough permissions to perform such requests. _More about [Meilisearch security](/learn/security/basic_security)._ We recommend running the scraper at each new deployment of your documentation, [as we do for the Meilisearch's one](https://github.com/meilisearch/documentation/blob/main/.github/workflows/scraper.yml). ### Integrate the search bar If your documentation is not a VuePress application, you can directly go to [this section](#for-all-kinds-of-documentation). #### For VuePress documentation sites If you use VuePress for your documentation, we provide a [Vuepress plugin](https://github.com/meilisearch/vuepress-plugin-meilisearch). This plugin is used in production in the Meilisearch documentation. In your VuePress project: ```bash yarn add vuepress-plugin-meilisearch ``` ```bash npm install vuepress-plugin-meilisearch ``` In your `config.js` file: ```js module.exports = { plugins: [ [ "vuepress-plugin-meilisearch", { "hostUrl": "", "apiKey": "", "indexUid": "docs" } ], ], } ``` The `hostUrl` and the `apiKey` fields are the credentials of the Meilisearch instance. Following on from this tutorial, they are respectively `MEILISEARCH_URL` and `MASTER_KEY`. `indexUid` is the index identifier in your Meilisearch instance in which your website content is stored. It has been defined in the [config file](#configuration-file). These three fields are mandatory, but more [optional fields are available](https://github.com/meilisearch/vuepress-plugin-meilisearch#customization) to customize your search bar. Since the configuration file is public, we strongly recommend providing a key that can only access [the search endpoint](/reference/api/search) , such as the `Default Search API Key`, in a production environment. Read m