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:
- Decide which attributes you want to use for sorting
- Add those attributes to the
sortableAttributes
index setting - 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:
[
{
"id": 1,
"title": "Solaris",
"author": "Stanislaw Lem",
"genres": [
"science fiction"
],
"price": 5.00
},
{
"id": 2,
"title": "The Parable of the Sower",
"author": "Octavia E. Butler",
"genres": [
"science fiction"
],
"price": 10.00
},
{
"id": 3,
"title": "Gender Trouble",
"author": "Judith Butler",
"genres": [
"feminism",
"philosophy"
],
"price": 10.00
},
{
"id": 4,
"title": "Wild Seed",
"author": "Octavia E. Butler",
"genres": [
"fantasy"
],
"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
:
<>
cURL
JS
Python
PHP
Java
Ruby
Go
Rust
Swift
Dart
curl \
-X POST 'http://localhost:7700/indexes/books/settings/sortable-attributes' \
-H 'Content-Type: application/json' \
--data-binary '[
"author",
"price"
]'
client.index('books').updateSortableAttributes([
'author',
'price'
])
client.index('books').update_sortable_attributes([
'author',
'price'
])
$client->index('books')->updateSortableAttributes([
'author',
'price'
]);
Settings settings = new Settings();
settings.setSortableAttributes(new String[] {"price", "author"});
client.index("books").updateSettings(settings);
client.index('books').update_sortable_attributes(['author', 'price'])
sortableAttributes := []string{
"author",
"price",
}
client.Index("books").UpdateSortableAttributes(&sortableAttributes)
let sortable_attributes = [
"author",
"price"
];
let progress: Progress = books.set_sortable_attributes(&sortable_attributes).await.unwrap();
client.index("books").updateSortableAttributes(["price", "author"]) { (result: Result<Update, Swift.Error>) in
switch result {
case .success(let update):
print(update)
case .failure(let error):
print(error)
}
}
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:
[
"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.
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
curl \
-X POST 'http://localhost:7700/indexes/books/settings/ranking-rules' \
-H 'Content-Type: application/json' \
--data-binary '[
"words",
"sort",
"typo",
"proximity",
"attribute",
"exactness"
]'
client.index('books').updateRankingRules([
'words',
'sort',
'typo',
'proximity',
'attribute',
'exactness'
])
client.index('books').update_ranking_rules([
'words',
'sort',
'typo',
'proximity',
'attribute',
'exactness'
])
$client->index('books')->updateRankingRules([
'words',
'sort',
'typo',
'proximity',
'attribute',
'exactness'
]);
Settings settings = new Settings();
settings.setRankingRules(new String[]
{
"words",
"sort",
"typo",
"proximity",
"attribute",
"exactness"
});
client.index("books").updateSettings(settings);
client.index('books').update_ranking_rules([
'words',
'sort',
'typo',
'proximity',
'attribute',
'exactness'
])
rankingRules := []string{
"words",
"sort",
"typo",
"proximity",
"attribute",
"exactness",
}
client.Index("books").UpdateRankingRules(&rankingRules)
let ranking_rules = [
"words",
"sort",
"typo",
"proximity",
"attribute",
"exactness"
];
let progress: Progress = books.set_ranking_rules(&ranking_rules).await.unwrap();
let rankingRules: [String] = [
"words",
"sort",
"typo",
"proximity",
"attribute",
"exactness"
]
client.index("books").updateRankingRules(rankingRules) { (result: Result<Update, Swift.Error>) in
switch result {
case .success(let update):
print(update)
case .failure(let error):
print(error)
}
}
await client.index('books').updateRankingRules([
'words',
'sort',
'typo',
'proximity',
'attribute',
'exactness'
]);
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:
"sort": [
"price:asc",
"author:desc"
]
When using the GET
route, sort
expects a comma-separated string:
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
curl \
-X POST 'http://localhost:7700/indexes/books/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "science fiction",
"sort": [
"price:asc"
]
}'
client.index('books').search('science fiction', {
sort: ['price:asc'],
})
client.index('books').search('science fiction', {
'sort': ['price:asc']
})
$client->index('books')->search('science fiction', ['sort' => ['price:asc']]);
SearchRequest searchRequest = new SearchRequest("science fiction").setSort(new String[] {"price:asc"});
client.index("books").search(searchRequest);
client.index('books').search('science fiction', { sort: ['price:asc'] })
resp, err := client.Index("books").Search("science fiction", &meilisearch.SearchRequest{
Sort: []string{
"price:asc",
},
})
let results: SearchResults<Books> = books.search()
.with_query("science fiction")
.with_sort(&["price:asc"])
.execute()
.await
.unwrap();
let searchParameters = SearchParameters(
query: "science fiction",
sort: ["price:asc"]
)
client.index("books").search(searchParameters) { (result: Result<SearchResult<Movie>, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
await client.index('books').search('science fiction', sort: ['price:asc']);
With our example dataset, the results look like this:
[
{
"id": 1,
"title": "Solaris",
"author": "Stanislaw Lem",
"genres": [
"science fiction"
],
"price": 5.00
},
{
"id": 2,
"title": "The Parable of the Sower",
"author": "Octavia E. Butler",
"genres": [
"science fiction"
],
"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:
<>
cURL
JS
Python
PHP
Java
Ruby
Go
Rust
Swift
Dart
curl \
-X POST 'http://localhost:7700/indexes/books/search' \
-H 'Content-Type: application/json' \
--data-binary '{
"q": "butler",
"sort": [
"author:desc"
]
}'
client.index('books').search('butler', {
sort: ['author:desc'],
})
client.index('books').search('butler', {
'sort': ['author:desc']
})
$client->index('books')->search('butler', ['sort' => ['author:desc']]);
SearchRequest searchRequest = new SearchRequest("butler").setSort(new String[] {"author:desc"});
client.index("books").search(searchRequest);
client.index('books').search('butler', { sort: ['author:desc'] })
resp, err := client.Index("books").Search("butler", &meilisearch.SearchRequest{
Sort: []string{
"author:desc",
},
})
let results: SearchResults<Books> = books.search()
.with_query("butler")
.with_sort(&["author:desc"])
.execute()
.await
.unwrap();
let searchParameters = SearchParameters(
query: "butler",
sort: ["author:desc"]
)
client.index("books").search(searchParameters) { (result: Result<SearchResult<Movie>, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
await client.index('books').search('butler', sort: ['author:desc']);
[
{
"id": 2,
"title": "The Parable of the Sower",
"author": "Octavia E. Butler",
"genres": [
"science fiction"
],
"price": 10.00
},
{
"id": 5,
"title": "Wild Seed",
"author": "Octavia E. Butler",
"genres": [
"fantasy"
],
"price": 5.00
},
{
"id": 4,
"title": "Gender Trouble",
"author": "Judith Butler",
"genres": [
"feminism",
"philosophy"
],
"price": 10.00
}
]
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
):
{
"sort": [
"_geoPoint(0.0, 0.0):asc"
]
}
Queries using _geoPoint
will always include a geoDistance
field containing the distance in meters between the document location and the _geoPoint
:
[
{
"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 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
curl -X POST 'http://localhost:7700/indexes/restaurants/search' \
-H 'Content-type:application/json' \
--data-binary '{ "sort": ["_geoPoint(48.8583701,2.2922926):asc"] }'
client.index('restaurants').search('', {
sort: ['_geoPoint(48.8583701,2.2922926):asc'],
})
client.index('restaurants').search('', {
'sort': ['_geoPoint(48.8583701,2.2922926):asc']
})
$client->index('restaurants')->search('', ['sort' => ['_geoPoint(48.8583701,2.2922926):asc']]);
SearchRequest searchRequest =
new SearchRequest("").setSort(new String[] {"_geoPoint(48.8583701,2.2922926):asc"});
client.index("restaurants").search(searchRequest);
client.index('restaurants').search('', { sort: ['_geoPoint(48.8583701,2.2922926):asc'] })
resp, err := client.Index("restaurants").Search("", &meilisearch.SearchRequest{
Sort: []string{
"_geoPoint(48.8583701,2.2922926):asc",
},
})
let results: SearchResults<Restaurant> = restaurants.search()
.with_sort(&["_geoPoint(48.8583701,2.2922926):asc"])
.execute()
.await
.unwrap();
let searchParameters = SearchParameters(
sort: ["_geoPoint(48.8583701,2.2922926):asc"]
)
client.index("restaurants").search(searchParameters) { (result: Result<SearchResult<Movie>, Swift.Error>) in
switch result {
case .success(let searchResult):
print(searchResult)
case .failure(let error):
print(error)
}
}
await client.index('restaurants').search('', sort: ['_geoPoint(48.8583701,2.2922926):asc']);