Reference
Meilisearch facets and filters: a complete guide
In Meilisearch, "facets" are the per-attribute counts you display on a faceted search UI ("12 products in red, 8 in blue"). "Filters" are the boolean expressions that narrow search results ("price < 50 AND brand = nike"). Both are powered by the same setting: filterableAttributes. Configure them once, use them in many ways.
The 30-second answer
- Add an attribute to
filterableAttributesto make it both filterable and facet-able. - Use the
filterquery parameter at search time to narrow results. - Use the
facetsquery parameter at search time to get per-value counts for the UI. - Both can be combined in the same query.
Step 1: declare filterable attributes
PATCH /indexes/products/settings/filterable-attributes
[
"brand",
"category",
"color",
"price",
"in_stock",
"rating"
]
Adding an attribute here triggers a reindex — Meilisearch builds an inverted index for each filterable field. After the reindex completes, the attribute is queryable in both filter and facets.
Step 2: filter at query time
The filter parameter accepts a SQL-like expression with AND, OR, and NOT:
POST /indexes/products/search
{
"q": "running shoes",
"filter": "brand = nike AND price < 100 AND in_stock = true"
}
Comparison operators
| Operator | Example | Meaning |
|---|---|---|
= | brand = nike | Equals |
!= | brand != nike | Not equals |
< > | price < 100 | Less than / greater than |
<= >= | rating >= 4 | Less or equal / greater or equal |
TO | price 50 TO 200 | Between (inclusive) |
IN | brand IN [nike, adidas] | Any of the listed values |
EXISTS | discount EXISTS | Field is present (since v1.5) |
IS NULL | discount IS NULL | Field is null (since v1.10) |
IS EMPTY | tags IS EMPTY | Array field is empty (since v1.10) |
Step 3: get facet counts at query time
Pass the facets parameter with the list of attributes you want counts for:
POST /indexes/products/search
{
"q": "running shoes",
"filter": "in_stock = true",
"facets": ["brand", "color", "category"]
}
The response includes a facetDistribution object:
{
"hits": [...],
"facetDistribution": {
"brand": {
"nike": 24,
"adidas": 18,
"asics": 9,
"brooks": 6
},
"color": {
"black": 30,
"white": 15,
"blue": 8
},
"category": {
"running": 57
}
}
}
Use this to render the "Brand: Nike (24), Adidas (18)..." UI on your search results page.
The "facets after the filter" rule
By default, the facet counts reflect only the documents that pass the current filter. If you filter in_stock = true, the brand facet shows counts of in-stock products only.
This is usually what you want — but it's wrong for the common e-commerce pattern of "show all brands, but greyed out and zero for ones with no in-stock items". For that pattern you need to run two queries: one for the result set and one for the unfiltered facet counts.
Building a fast filter UI
For a responsive faceted UI, follow this pattern:
- Initial page load: one search with no filter, all facets requested. Render the full facet counts.
- User clicks a filter: immediately update the URL state, then issue a new search with the filter applied and the same facet list.
- Update the UI: render the new hits and the new facet counts. Some facet values may now have count 0 — show them as disabled rather than removing them, so the UI stays stable.
- Debounce text input: 200ms is the sweet spot for typed search. Use the same query+filter shape for every keystroke.
Numeric ranges and histograms
Meilisearch returns flat counts per value, not ranges. If you want a price histogram (e.g. "0–50: 12 products, 50–100: 24 products"), you compute the buckets in your application from the raw facetDistribution response. Meilisearch doesn't do server-side bucketing.
Performance tips
- Don't add every attribute to
filterableAttributes. Each one increases index size and indexing time. Add only the ones you actually filter or facet on. - Numeric filters are faster than string filters. Filter on price, ratings, dates as numbers, not as strings.
- Use
maxValuesPerFacetin the indexfacetingsettings to cap how many distinct values are returned per facet (default 100). If you have a brand attribute with 50,000 distinct values, capping it makes the response smaller.
FAQ
What's the difference between filterable and sortable attributes?
An attribute can be filterable (used in filter), sortable (used in sort), both, or neither. They're separate settings — adding to one doesn't add to the other. Most numeric attributes you'd filter on (price, rating) you also want to sort on, so they end up in both lists.
Can I filter on array fields?
Yes. tags = sale matches any document where the tags array contains "sale". This is the most common way to do "category" or "tag" filters.
Can filters be case-insensitive?
Filter values are case-insensitive by default. brand = Nike and brand = nike match the same documents.
How does AdminDex help with filterable attributes?
The settings editor has an attribute picker that shows every field name from your real index, so you don't have to type them from memory. Click to add a field to the filterable list, and AdminDex sends the PATCH request with the right format.
Stop running Meilisearch from a terminal.
AdminDex gives you a visual dashboard for every Meilisearch instance you own — settings, API keys, tasks, and search analytics.
Related reading
-
Meilisearch index settings: the ones that actually matter
Meilisearch exposes 13 settings per index. Most teams only need to touch four of them. Here's the priority order and what each one does.
-
Tuning Meilisearch ranking rules: a visual walkthrough
How Meilisearch's ranking rules work, what each of the six default rules does, and how to reorder them for your specific search use case.
-
Reading Meilisearch search analytics: zero-result queries and what to do about them
How to interpret Meilisearch search analytics — what to look for, which numbers signal a real problem, and what to do when you spot them.
Spotted an inaccuracy? Email the AdminDex team →