Sorting

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.

TIP

Sorting at search time can be particularly effective when combined with placeholder searches.

Configuring 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 (optional)

Select attributes for sorting

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.

Currently, fields containing nested arrays and objects will be silently ignored.

WARNING

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, so we strongly recommend you only allow sorting at query time on fields containing the same type of value.

Adding attributes to sortableAttributes

After you have decided which fields you will allow your users to sort on, you must add their attributes to the sortableAttributes index setting.

sortableAttributes accepts an array of strings, each corresponding to one attribute. Note that the attribute order in sortableAttributes has no impact on sorting.

Example

Suppose you have collection of books containing the following fields:

  1. [
  2. {
  3. "id": 1,
  4. "title": "Solaris",
  5. "author": "Stanislaw Lem",
  6. "genres": [
  7. "science fiction"
  8. ],
  9. "price": 5.00
  10. },
  11. {
  12. "id": 2,
  13. "title": "The Parable of the Sower",
  14. "author": "Octavia E. Butler",
  15. "genres": [
  16. "science fiction"
  17. ],
  18. "price": 10.00
  19. },
  20. {
  21. "id": 3,
  22. "title": "Gender Trouble",
  23. "author": "Judith Butler",
  24. "genres": [
  25. "feminism",
  26. "philosophy"
  27. ],
  28. "price": 10.00
  29. },
  30. {
  31. "id": 4,
  32. "title": "Wild Seed",
  33. "author": "Octavia E. Butler",
  34. "genres": [
  35. "fantasy"
  36. ],
  37. "price": 5.00
  38. },
  39. ]

If you are using this dataset in a webshop, you might want to allow your users to sort on author and price:

<>

cURL

JS

Python

PHP

Java

Ruby

Go

Rust

Swift

Dart

  1. curl \
  2. -X POST 'http://localhost:7700/indexes/books/settings/sortable-attributes' \
  3. -H 'Content-Type: application/json' \
  4. --data-binary '[
  5. "author",
  6. "price"
  7. ]'
  1. client.index('books').updateSortableAttributes([
  2. 'author',
  3. 'price'
  4. ])
  1. client.index('books').update_sortable_attributes([
  2. 'author',
  3. 'price'
  4. ])
  1. $client->index('books')->updateSortableAttributes([
  2. 'author',
  3. 'price'
  4. ]);
  1. Settings settings = new Settings();
  2. settings.setSortableAttributes(new String[] {"price", "author"});
  3. client.index("books").updateSettings(settings);
  1. client.index('books').update_sortable_attributes(['author', 'price'])
  1. sortableAttributes := []string{
  2. "author",
  3. "price",
  4. }
  5. client.Index("books").UpdateSortableAttributes(&sortableAttributes)
  1. let sortable_attributes = [
  2. "author",
  3. "price"
  4. ];
  5. let progress: Progress = books.set_sortable_attributes(&sortable_attributes).await.unwrap();
  1. client.index("books").updateSortableAttributes(["price", "author"]) { (result: Result<Update, Swift.Error>) in
  2. switch result {
  3. case .success(let update):
  4. print(update)
  5. case .failure(let error):
  6. print(error)
  7. }
  8. }
  1. await client.index('books').updateSortableAttributes(['author', 'price']);

Customize ranking rule order (optional)

When users sort results at search time, MeiliSearch’s ranking rules 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:

  1. [
  2. "words",
  3. "typo",
  4. "proximity",
  5. "attribute",
  6. "sort",
  7. "exactness"
  8. ]

"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.

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:

<>

cURL

JS

Python

PHP

Java

Ruby

Go

Rust

Swift

Dart

  1. curl \
  2. -X POST 'http://localhost:7700/indexes/books/settings/ranking-rules' \
  3. -H 'Content-Type: application/json' \
  4. --data-binary '[
  5. "words",
  6. "sort",
  7. "typo",
  8. "proximity",
  9. "attribute",
  10. "exactness"
  11. ]'
  1. client.index('books').updateRankingRules([
  2. 'words',
  3. 'sort',
  4. 'typo',
  5. 'proximity',
  6. 'attribute',
  7. 'exactness'
  8. ])
  1. client.index('books').update_ranking_rules([
  2. 'words',
  3. 'sort',
  4. 'typo',
  5. 'proximity',
  6. 'attribute',
  7. 'exactness'
  8. ])
  1. $client->index('books')->updateRankingRules([
  2. 'words',
  3. 'sort',
  4. 'typo',
  5. 'proximity',
  6. 'attribute',
  7. 'exactness'
  8. ]);
  1. Settings settings = new Settings();
  2. settings.setRankingRules(new String[]
  3. {
  4. "words",
  5. "sort",
  6. "typo",
  7. "proximity",
  8. "attribute",
  9. "exactness"
  10. });
  11. client.index("books").updateSettings(settings);
  1. client.index('books').update_ranking_rules([
  2. 'words',
  3. 'sort',
  4. 'typo',
  5. 'proximity',
  6. 'attribute',
  7. 'exactness'
  8. ])
  1. rankingRules := []string{
  2. "words",
  3. "sort",
  4. "typo",
  5. "proximity",
  6. "attribute",
  7. "exactness",
  8. }
  9. client.Index("books").UpdateRankingRules(&rankingRules)
  1. let ranking_rules = [
  2. "words",
  3. "sort",
  4. "typo",
  5. "proximity",
  6. "attribute",
  7. "exactness"
  8. ];
  9. let progress: Progress = books.set_ranking_rules(&ranking_rules).await.unwrap();
  1. let rankingRules: [String] = [
  2. "words",
  3. "sort",
  4. "typo",
  5. "proximity",
  6. "attribute",
  7. "exactness"
  8. ]
  9. client.index("books").updateRankingRules(rankingRules) { (result: Result<Update, Swift.Error>) in
  10. switch result {
  11. case .success(let update):
  12. print(update)
  13. case .failure(let error):
  14. print(error)
  15. }
  16. }
  1. await client.index('books').updateRankingRules([
  2. 'words',
  3. 'sort',
  4. 'typo',
  5. 'proximity',
  6. 'attribute',
  7. 'exactness'
  8. ]);

Sorting results at search time

After configuring sortableAttributes, you can use the sort search parameter to control the sorting order of your search results.

Using sort

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:

  1. "sort": [
  2. "price:asc",
  3. "author:desc"
  4. ]

When using the GET route, sort expects a comma-separated string:

  1. sort="price:desc,author:asc"

We strongly recommend using POST over GET routes whenever possible.

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:

<>

cURL

JS

Python

PHP

Java

Ruby

Go

Rust

Swift

Dart

  1. curl \
  2. -X POST 'http://localhost:7700/indexes/books/search' \
  3. -H 'Content-Type: application/json' \
  4. --data-binary '{
  5. "q": "science fiction",
  6. "sort": [
  7. "price:asc"
  8. ]
  9. }'
  1. client.index('books').search('science fiction', {
  2. sort: ['price:asc'],
  3. })
  1. client.index('books').search('science fiction', {
  2. 'sort': ['price:asc']
  3. })
  1. $client->index('books')->search('science fiction', ['sort' => ['price:asc']]);
  1. SearchRequest searchRequest = new SearchRequest("science fiction").setSort(new String[] {"price:asc"});
  2. client.index("books").search(searchRequest);
  1. client.index('books').search('science fiction', { sort: ['price:asc'] })
  1. resp, err := client.Index("books").Search("science fiction", &meilisearch.SearchRequest{
  2. Sort: []string{
  3. "price:asc",
  4. },
  5. })
  1. let results: SearchResults<Books> = books.search()
  2. .with_query("science fiction")
  3. .with_sort(&["price:asc"])
  4. .execute()
  5. .await
  6. .unwrap();
  1. let searchParameters = SearchParameters(
  2. query: "science fiction",
  3. sort: ["price:asc"]
  4. )
  5. client.index("books").search(searchParameters) { (result: Result<SearchResult<Movie>, Swift.Error>) in
  6. switch result {
  7. case .success(let searchResult):
  8. print(searchResult)
  9. case .failure(let error):
  10. print(error)
  11. }
  12. }
  1. await client.index('books').search('science fiction', sort: ['price:asc']);

With our example dataset, the results look like this:

  1. [
  2. {
  3. "id": 1,
  4. "title": "Solaris",
  5. "author": "Stanislaw Lem",
  6. "genres": [
  7. "science fiction"
  8. ],
  9. "price": 5.00
  10. },
  11. {
  12. "id": 2,
  13. "title": "The Parable of the Sower",
  14. "author": "Octavia E. Butler",
  15. "genres": [
  16. "science fiction"
  17. ],
  18. "price": 10.00
  19. }
  20. ]

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:

<>

cURL

JS

Python

PHP

Java

Ruby

Go

Rust

Swift

Dart

  1. curl \
  2. -X POST 'http://localhost:7700/indexes/books/search' \
  3. -H 'Content-Type: application/json' \
  4. --data-binary '{
  5. "q": "butler",
  6. "sort": [
  7. "author:desc"
  8. ]
  9. }'
  1. client.index('books').search('butler', {
  2. sort: ['author:desc'],
  3. })
  1. client.index('books').search('butler', {
  2. 'sort': ['author:desc']
  3. })
  1. $client->index('books')->search('butler', ['sort' => ['author:desc']]);
  1. SearchRequest searchRequest = new SearchRequest("butler").setSort(new String[] {"author:desc"});
  2. client.index("books").search(searchRequest);
  1. client.index('books').search('butler', { sort: ['author:desc'] })
  1. resp, err := client.Index("books").Search("butler", &meilisearch.SearchRequest{
  2. Sort: []string{
  3. "author:desc",
  4. },
  5. })
  1. let results: SearchResults<Books> = books.search()
  2. .with_query("butler")
  3. .with_sort(&["author:desc"])
  4. .execute()
  5. .await
  6. .unwrap();
  1. let searchParameters = SearchParameters(
  2. query: "butler",
  3. sort: ["author:desc"]
  4. )
  5. client.index("books").search(searchParameters) { (result: Result<SearchResult<Movie>, Swift.Error>) in
  6. switch result {
  7. case .success(let searchResult):
  8. print(searchResult)
  9. case .failure(let error):
  10. print(error)
  11. }
  12. }
  1. await client.index('books').search('butler', sort: ['author:desc']);
  1. [
  2. {
  3. "id": 2,
  4. "title": "The Parable of the Sower",
  5. "author": "Octavia E. Butler",
  6. "genres": [
  7. "science fiction"
  8. ],
  9. "price": 10.00
  10. },
  11. {
  12. "id": 5,
  13. "title": "Wild Seed",
  14. "author": "Octavia E. Butler",
  15. "genres": [
  16. "fantasy"
  17. ],
  18. "price": 5.00
  19. },
  20. {
  21. "id": 4,
  22. "title": "Gender Trouble",
  23. "author": "Judith Butler",
  24. "genres": [
  25. "feminism",
  26. "philosophy"
  27. ],
  28. "price": 10.00
  29. }
  30. ]

Sorting and custom ranking rules

There is a lot of overlap between sorting and configuring 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.

Sorting with _geoPoint

If your documents contain _geo data, you can use _geoPoint to sort results based on their distance from a geographic position.

_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):

  1. {
  2. "sort": [
  3. "_geoPoint(0.0, 0.0):asc"
  4. ]
  5. }

Queries using _geoPoint will always include a geoDistance field containing the distance in meters between the document location and the _geoPoint:

  1. [
  2. {
  3. "id": 1,
  4. "name": "Nàpiz' Milano",
  5. "_geo": {
  6. "lat": 45.4777599,
  7. "lng": 9.1967508
  8. },
  9. "_geoDistance": 1532
  10. }
  11. ]

You can read more about location-based sorting in our geosearch guide.

Example

The following query will sort results based on how close they are to the Eiffel Tower:

<>

cURL

JS

Python

PHP

Java

Ruby

Go

Rust

Swift

Dart

  1. curl -X POST 'http://localhost:7700/indexes/restaurants/search' \
  2. -H 'Content-type:application/json' \
  3. --data-binary '{ "sort": ["_geoPoint(48.8583701,2.2922926):asc"] }'
  1. client.index('restaurants').search('', {
  2. sort: ['_geoPoint(48.8583701,2.2922926):asc'],
  3. })
  1. client.index('restaurants').search('', {
  2. 'sort': ['_geoPoint(48.8583701,2.2922926):asc']
  3. })
  1. $client->index('restaurants')->search('', ['sort' => ['_geoPoint(48.8583701,2.2922926):asc']]);
  1. SearchRequest searchRequest =
  2. new SearchRequest("").setSort(new String[] {"_geoPoint(48.8583701,2.2922926):asc"});
  3. client.index("restaurants").search(searchRequest);
  1. client.index('restaurants').search('', { sort: ['_geoPoint(48.8583701,2.2922926):asc'] })
  1. resp, err := client.Index("restaurants").Search("", &meilisearch.SearchRequest{
  2. Sort: []string{
  3. "_geoPoint(48.8583701,2.2922926):asc",
  4. },
  5. })
  1. let results: SearchResults<Restaurant> = restaurants.search()
  2. .with_sort(&["_geoPoint(48.8583701,2.2922926):asc"])
  3. .execute()
  4. .await
  5. .unwrap();
  1. let searchParameters = SearchParameters(
  2. sort: ["_geoPoint(48.8583701,2.2922926):asc"]
  3. )
  4. client.index("restaurants").search(searchParameters) { (result: Result<SearchResult<Movie>, Swift.Error>) in
  5. switch result {
  6. case .success(let searchResult):
  7. print(searchResult)
  8. case .failure(let error):
  9. print(error)
  10. }
  11. }
  1. await client.index('restaurants').search('', sort: ['_geoPoint(48.8583701,2.2922926):asc']);