Filtering

When searching for documents, it is sometimes desired to filter through the results based on criteria, to provide the user with the most relevant response possible.

Meilisearch allows you to define filters thanks to a very simple query language. Once defined, you can pass your filter to your search query, as a parameter.

Using Filters

In order to apply filtering to a search, simply add a filter expression to the filters query parameter of your search. Find out more on search query parameters in our API reference.

The Query Language

In itself, the query language is very simple and allows you to filter results on any document field. For now, it only allows you to query on fields that are either number, boolean, string, or an array of the aforementioned. Filtering on nested arrays or objects isn’t permitted.

Conditions

Conditions are the primitives of query filters. They are composed of three mandatory parameters field OP value where:

  • field refers to the document attribute to filter on (e.g id, title…). It is either a single alphanumeric word or a quoted string: "movie title", and release_date are both valid field.
  • OP is the comparison operator, it can be one of =, !=, >, >=, <, or <=.
  • value is the test condition for which the filter shall filter upon.

The = and != operators check for equality and difference. For strings, they are both case insensitive:

  1. title = "american ninja" // matches "American Ninja"

NOTE

string values are either double-quoted, single-quoted or a single unquoted word: title = "Scream", title = 'Scream' and title = Scream are all valid syntaxes, title = The Avengers is not.

The >, >=, <, and <= apply only to numerical values, and behave the standard way.

WARNING

As no specific schema is enforced at indexing, the filtering engine will try to coerce the type of value. This can lead to undefined behaviour when big floats are coerced into integers and reciprocally. For this reason, it is best to have homogeneous typing across fields, especially if numbers tend to become large.

Expressions

The simplest form of expressions are conditions. New expressions are created either by connecting other expressions together with logical connectives, or by grouping expressions with parentheses, e.g:

  1. title = Scream // a condition is an expression
  2. title = Scream OR title = "The Avengers" // also an expression
  3. (title = Scream OR title = "The Avengers") // use parentheses to isolate an expression
  4. NOT (title = Scream OR title = "The Avengers") // negate the whole expression
  5. rating >= 3 AND (NOT (title = Scream OR title = "The Avengers")) // and so on...

Logical Connectives

An arbitrary number of expressions can be connected together thanks to logical connectives. These connectives are:

  • NOT negates the following expression, e.g. NOT title = Scream.
  • OR performs a logical ‘or’ between two expressions, e.g. title = Scream OR title = "the avengers"
  • AND performs a logical ‘and’ between two expressions, e.g. title = Dumbo AND title = "Tim Burton"

NOTE

NOT has the highest precedence.
NOT precedence is higher than AND.
AND precedence is higher than OR.
OR has the lowest precedence.
This means that NOT title = Scream OR title = "The Avengers" is effectively evaluated (NOT title = Scream) OR title = "The Avengers", and title = Scream OR title = "The Avengers" AND release_date > 795484800 is effectively evaluated title = Scream OR (title = "The Avengers" AND release_date > 795484800)

A Note on Performance

MeiliSearch is intended to be a flexible tool. For this reason, we decided not to put any restrictions on which fields the user can filter on, nor on how the queries are built. It is important to understand that it can lead to performance issues in some cases. We are currently working on new features to address this issue, such as faceting Filtering - 图1 (opens new window) to allow you to efficiently narrow down the number of candidates to a query, and thus improving performances of plain filters, even in worst cases.

Also consider that OR and AND are left associative, meaning that the left-hand side is always evaluated first. If the left-hand side of an OR is true or the left-hand side of a AND is false, then the right-hand side won’t be evaluated. You can sometimes get performance improvements by building your queries in such a way that the right-hand side of a connective is evaluated only when necessary.

Examples

Suppose that you have a collection of movies, in the following JSON format:

  1. [
  2. {
  3. "id": "495925",
  4. "title": "Doraemon the Movie:Nobita's Treasure Island",
  5. "director": "Fujiko Fujio",
  6. "poster": "https://image.tmdb.org/t/p/w1280/cmJ71gdZxCqkMUvGwWgSg3MK7pC.jpg",
  7. "overview": "The story is based on Robert Louis Stevenson's Treasure Island novel.",
  8. "release_date": 1520035200
  9. },
  10. {
  11. "id": "329996",
  12. "title": "Dumbo",
  13. "director": "Tim Burton",
  14. "poster": "https://image.tmdb.org/t/p/w1280/279PwJAcelI4VuBtdzrZASqDPQr.jpg",
  15. "overview": "A young elephant, whose oversized ears enable him to fly, helps...",
  16. "release_date": 1553644800
  17. },
  18. {
  19. "id": "299536",
  20. "title": "Avengers:Infinity War",
  21. "director": "Joe Russo",
  22. "poster": "https://image.tmdb.org/t/p/w1280/7WsyChQLEftFiDOVTGkv3hFpyyt.jpg",
  23. "overview": "As the Avengers and their allies have continued to protect...",
  24. "release_date": 1524618000
  25. },
  26. {
  27. "id": "458723",
  28. "title": "Us",
  29. "director": "Jordan Peele",
  30. "poster": "https://image.tmdb.org/t/p/w1280/ux2dU1jQ2ACIMShzB3yP93Udpzc.jpg",
  31. "overview": "Husband and wife Gabe and Adelaide Wilson take their...",
  32. "release_date": 1552521600
  33. },
  34. ...
  35. ]

Say you want to only show to your user movies that were released after a certain date, then you can use the following filter:

  1. release_date > 795484800 // march 18, 1995

Querying on Avengers, the above example results in the following command:

  1. curl 'http://localhost:7700/indexes/movies/search' \
  2. --data '{ "q": "Avengers", "filters": "release_date > 795484800" }'
  1. client.index('movies').search('Avengers', {
  2. filters: 'release_date > 795484800'
  3. })
  1. client.index('movies').search('Avengers', {
  2. 'filters': 'release_date > 795484800'
  3. })
  1. $client->index('movies')->search('Avengers', ['filters' => 'release_date > 795484800']);
  1. index.search('Avengers', { filters: 'release_date > 795484800' })
  1. results, error := client.Search("movies").Search(meilisearch.SearchRequest{
  2. Query: "Avengers",
  3. Filters: "release_date > \"795484800\"",
  4. })
  1. let results: SearchResults<Movie> = movies.search()
  2. .with_query("Avengers")
  3. .with_filters("release_date > 795484800")
  4. .execute()
  5. .await
  6. .unwrap();

Now imagine that we only want Batman movies released after the 18 of march 1995, and directed by either Tim Burton or Christopher Nolan, then you would use this filter:

  1. release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")

Querying on Batman, the above example results in the following command:

  1. curl 'http://localhost:7700/indexes/movies/search' \
  2. --data '{ "q":"Batman", "filters": "release_date > 795484800 AND (director = \"Tim Burton\" OR director = \"Christopher Nolan\")" }'
  1. client.index('movies').search('Batman', {
  2. filters: 'release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")'
  3. })
  1. client.index('movies').search('Batman', {
  2. 'filters': 'release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")'
  3. })
  1. $client->index('movies')->search('Avengers', ['filters' => 'release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")']);
  1. index.search('Batman', {
  2. filters: 'release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")'
  3. })
  1. results, error := client.Search("movies").Search(meilisearch.SearchRequest{
  2. Query: "Batman",
  3. Filters: "release_date > 795484800 AND (director = \"Tim Burton\" OR director = \"Christopher Nolan\")",
  4. })
  1. let results: SearchResults<Movie> = movies.search()
  2. .with_query("Batman")
  3. .with_filters(r#"release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")"#)
  4. .execute()
  5. .await
  6. .unwrap();

Note that filtering on string is case insensitive. Here, the parentheses are mandatory, as AND has a higher precedence.

If we want to select only horror movies from our favorite director:

  1. director = "Jordan Peele"

Querying on horror, the above example results in the following command:

  1. curl 'http://localhost:7700/indexes/movies/search' \
  2. --data '{ "q": "horror", "filters": "director = \"Jordan Peele\"" }'
  1. client.index('movies').search('horror', {
  2. filters: 'director = "Jordan Peele"'
  3. })
  1. client.index('movies').search('horror', {
  2. 'filters': 'director = "Jordan Peele"'
  3. })
  1. $client->index('movies')->search('horror', ['filters' => 'director = "Jordan Peele"']);
  1. index.search('horror', { filters: 'director = "Jordan Peele"' })
  1. results, error := client.Search("movies").Search(meilisearch.SearchRequest{
  2. Query: "horror",
  3. Filters: "director = \"Jordan Peele\"",
  4. })
  1. let results: SearchResults<Movie> = movies.search()
  2. .with_query("horror")
  3. .with_filters(r#"director = "Jordan Peele""#)
  4. .execute()
  5. .await
  6. .unwrap();

If we want to find the Planet of the Apes movies that have been well rated but we want to ignore the one directed by Tim Burton:

  1. rating >= 3 AND (NOT director = "Tim Burton")

Querying on Planet of the Apes, the above example results in the following command:

  1. curl 'http://localhost:7700/indexes/movies/search' \
  2. --data '{ "q": "Planet of the Apes", "filters": "rating >= 3 AND (NOT director = \"Tim Burton\")" }' \
  1. client.index('movies').search('Planet of the Apes', {
  2. filters: 'rating >= 3 AND (NOT director = "Tim Burton")'
  3. })
  1. client.index('movies').search('Planet of the Apes', {
  2. 'filters': 'rating >= 3 AND (NOT director = "Tim Burton")'
  3. })
  1. $client->index('movies')->search('Planet of the Apes', ['filters' => 'rating >= 3 AND (NOT director = "Tim Burton")']);
  1. index.search('Planet of the Apes', {
  2. filters: 'rating >= 3 AND (NOT director = "Tim Burton")'
  3. })
  1. results, error := client.Search("books").Search(meilisearch.SearchRequest{
  2. Query: "Planet of the Apes",
  3. Filters: "rating >= 3 AND (NOT director = \"Tim Burton\"",
  4. })
  1. let results: SearchResults<Movie> = movies.search()
  2. .with_query("Planet of the Apes")
  3. .with_filters(r#"rating >= 3 AND (NOT director = "Tim Burton")"#)
  4. .execute()
  5. .await
  6. .unwrap();

Finally, imagine that you want to filter on “id”. You would probably do this:

  1. id = "299536"

However, this would be a very bad idea. id uniquely identifies a movie. Therefore, only one document can match the user query. The filtering engine would have to search through all candidate documents to find the only possible match. This would be highly inefficient and should be avoided. We are currently working on a faceted search Filtering - 图2 (opens new window) feature, especially optimized for this kind of need. It is important to understand that filtering and querying don’t have the same performances.