Faceted search under active filters #4949
githubmanticore
announced in
Blog
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Originally published on the Manticore Search website on July 23, 2026
Faceted search under active filters
Manticore Search 25.12.0 adds strict, auto, and max facet modes so e-commerce filters can show selected, available, and unavailable values without manually building separate queries.
Facets in an online store seem simple until the first filter is selected.
In a catalog, they are part of the navigation. A selected color should not disappear from the list. Other colors should remain available: the user may want to switch to one or broaden the selection. Options with no matching products are more useful when shown as unavailable. Within a single facet,
ORusually applies: red or blue. Across different facets, it isAND: brand, color, and size at the same time.The difficult part begins after the first selection. Imagine a product catalog where the user has selected a brand, color, and size. The main result set must remain narrow and show only products matching all three conditions. But the filter panel follows different rules. It needs to preserve the selected color while also showing the colors the user can switch to under the same brand and size.
If every facet inherits all filters from the main query, it quickly collapses to the values already selected. If the filters for every facet have to be rebuilt manually, you end up with separate query branches for color, size, brand, availability, seller, and every other attribute.
Manticore Search 25.12.0 introduced
facet_filter_mode, which moves this behavior into the facet API. A query can now describe how the store's filter panel should behave, without the application manually building nearly identical queries for every facet.Try the behavior before implementing it. The live faceted storefront demo lets you switch among strict, auto, and max + zeroes, combine filters, and inspect the JSON request and native Manticore response.
Open the live faceted search demo
What e-commerce facets need
For a filter panel, answering "how many products have this value?" is not enough. It needs statuses that the interface can use directly:
IN (...);facet_filter_modehandles the most common part of this problem: recalculating facets after the user has already selected several filters.What changed
Facets now have three filter inheritance modes:
strictautostatus: selected values getselected, while other values getavailable.maxstatusto distinguish selected, available, and unavailable values.You can also control this manually:
ALL FILTERS- apply all filters to the facet;FILTERS color_id, size_id- apply only the listed filters;EXCLUDE FILTERS color_id- apply every filter except those listed;ZEROES- starting with Manticore Search 27.3.0, preserve buckets from the broadmaxscope in SQLmaxmode even when theircount(*)in the current facet is0.The same options are available in the JSON API through
facet_filter_mode,mode,filters,exclude_filters, andzeroes: true.In short,
strictanswers "what is in the current result set?",autoanswers "what happens if this particular filter changes?", andmaxshows a broad list of buckets with astatusfield:selected,available, orunavailable.Minimal example
Consider a small catalog with a brand, color, size, and SKU:
The user selected:
Under these conditions, the result set contains one product:
p1. Let's see what happens to the facets.strict: the previous behavior
Without additional options, Manticore Search uses
strict: every facet receives all filters from the main query.Response:
This is an accurate SQL response, but it is often too narrow for an online store. The user sees only the already selected
color_id=1andsize_id=10. It looks as if there are no other options, even though the data contains a product with the same brand and size but a different color (color_id=2), and another with the same brand and color but a different size (size_id=20).auto: a facet ignores its own filter
The
automode keeps every filter except the filter on the facet currently being calculated.Response:
Here is what happened:
FACET color_idappliedbrand_id=7 AND size_id=10, but notcolor_id=1;FACET size_idappliedbrand_id=7 AND color_id=1, but notsize_id=10.This gives the interface alternative values without a separate query for every facet. Selected buckets are marked
selected, while other values in the same facet are markedavailable: they can be added to the current filter as a broader selection throughIN (...).max: a broad bucket list with statuses
autoshows only values from the current filter scope for a particular facet.maxgoes further: it calculates buckets over the base result set and marks their status separately for the interface.Response:
statuscomes directly from Manticore Search:selected- the value is already present in the filter on this facet;available- the value can be selected; for another value in the same facet, this broadens the filter throughIN (...);unavailable- the value exists in the facet's broad result set, but selecting it would return no documents under the current filters.In this example,
color_id=1occurs in three products across the catalog, so its count inmaxis3. It is markedselectedbecause it already participates in the filter. The other colors are markedavailable: if the user selects one, the color filter becomes broader, for examplecolor_id IN (1,2). Unavailable values appear when a bucket in the broad result set cannot return documents under the current filters; theskuexample below demonstrates this case.Manually setting the filter scope
The global
facet_filter_modecovers most common cases, but sometimes different facets need different behavior. For example, color can remain strictly constrained by all filters, size can usemax, SKU can be calculated using only color and size, and brand can be calculated without the color filter.Response:
How to read this query:
FACET color_id ALL FILTERSapplies every filter and returns only the selected color;FACET size_idinherits the query-levelmaxmode;FACET sku FILTERS color_id, size_idapplies only the color and size filters;FACET brand_id EXCLUDE FILTERS color_idapplies every filter except color.This mode is useful when a filter panel contains both regular and technical facets, and some values need to be calculated according to special rules.
ZEROES: zero-count buckets in max
ZEROESis useful when counts need to remain strict but the list of values needs to stay broad. It works withmax, either throughOPTION facet_filter_mode='max'or throughMODE maxon an individual facet.Without
ZEROES, a facet withALL FILTERSshows only the bucket that passes every filter:Response:
With
ZEROES, Manticore Search keeps the same visible counts but returns the remaining buckets from the broadmaxscope with a count of zero:Response:
The interface can then show
largeandxlargealongside the selectedsmall: the number refers to the current strict result set, whilestatusshows that these values can still be selected to broaden the filter.The same query through the JSON API
SQL is shorter here and shows the mechanics more clearly, but the JSON API supports the same approach:
Response:
{ "took": 0, "timed_out": false, "hits": { "total": 1, "total_relation": "eq", "hits": [] }, "aggregations": { "colors": { "buckets": [ { "key": 1, "doc_count": 3, "status": "selected" }, { "key": 2, "doc_count": 1, "status": "available" }, { "key": 3, "doc_count": 1, "status": "available" }, { "key": 4, "doc_count": 1, "status": "available" }, { "key": 5, "doc_count": 1, "status": "available" } ] }, "sizes": { "buckets": [ { "key": 10, "doc_count": 4, "status": "selected" }, { "key": 20, "doc_count": 2, "status": "available" }, { "key": 30, "doc_count": 1, "status": "available" } ] } } }In JSON aggregations,
mode,filters,exclude_filters, andzeroescan also be set for each individual aggregation.How this differs from Meilisearch, Elasticsearch, and OpenSearch
We tested the same scenario in Manticore Search, Meilisearch, Elasticsearch, and OpenSearch: the
brand_id=7,color_id=1, andsize_id=10filters are active; the result set remains strict, while the facets need to show options that appear when their own filter is excluded.auto/maxauto/max;unavailableonly inmaxglobal+filteraggregationsstatusin the application.global+filteraggregationsBasic facets are easy to use in Meilisearch, but a query such as:
{ "filter": ["brand_id = 7", "color_id = 1", "size_id = 10"], "facets": ["color_id", "size_id"] }returns counts for the already filtered result set. In our dataset, that means only
color_id=1andsize_id=10. The alternativecolor_id=2andsize_id=20values do not appear in this response. If the interface needs them, the application must make additional queries and merge the results.In Elasticsearch and OpenSearch, similar behavior can be built in one query, but each facet needs its own explicit aggregation branch. For
color, you keepbrand_idandsize_id; forsize, you keepbrand_idandcolor_id; and so on. This works, but the query grows quickly, and bucket statuses still have to be calculated in the application.The main difference is where this logic lives. In Manticore Search, it is configured directly in the facet API; in other systems, it is usually assembled from several similar filter trees.
Performance and limitations
strictremains the default and preserves the previous behavior. If you only need facets within the current result set, nothing needs to change.autois usually the better fit for e-commerce filters: it shows alternative values within each facet, marks selected values asselected, and marks other values asavailable.Use
maxwhen the interface needs value lists broader than the current result set and needs to show unavailable options. This mode costs more: Manticore Search calculates buckets over a broad scope and then determines theirstatusseparately. This is worth considering with large datasets and many facets.There are also some limitations:
AND;AND/ORtrees are not rewritten automatically for individual facets;=andIN;For prices, discounts, and ratings, it is better to define separate ranges explicitly. For example, keep a numeric field for sorting and sliders, and add
price_bandordiscount_bandfor the facet. This lets the interface show clear statuses for predefined ranges.SEO for faceted URLs, A/B tests, merchandising rules, and query analytics remain the responsibility of the application or platform around search.
In practice, start with
autowhen you need alternative values, and move tomaxwhen the interface needs a broad bucket list withselected,available, andunavailablestatuses.Further reading
For an introduction to faceted search in Manticore Search, start with the earlier Faceted search article. It covers basic
FACETqueries, sorting, limits, and facets over expressions.See the
FACETdocumentation for details.For an interactive introduction to facets, take the Manticore Faceting course.
All reactions