Facets
Six types, and one counting rule that matters more than all of them.
The counting rule
A disjunctive facet (operator or) is counted with its own refinements excluded. That is what makes “Globex (12)” keep showing a useful number after the visitor ticks “Acme” — the count answers “what would I get if I ticked this too”, which is the question a visitor is actually asking.
A conjunctive facet (operator and) is the opposite: it counts against everything, including itself, because ticking a second value narrows rather than widens.
This asymmetry is the thing most hand-rolled implementations get wrong, and it is why a hand-built sidebar so often ends up showing zero next to every unticked option.
A refined value stays visible even at zero, so it can be un-ticked. A refinement naming a value that no longer exists — a bookmarked URL after the content changed — simply matches nothing rather than erroring.
The types
String
Free-text values: “Acme”, “Blue”. Refined by exact match. The ordinary case, and available on Lite.
Boolean
True/false, rendered as Yes/No. Real booleans throughout — a boolean facet never quietly becomes the string "true". Also available on Lite.
Hierarchical
Path values: Home > Tools > Saws. Refining a level narrows to it and everything under it.
Ancestors are expanded at build time, so by query time a hierarchical facet is an ordinary string facet and neither engine knows what a path is. The tree is rebuilt for the menu widget, from the flat values, at render time.
Numeric
Numbers, refined by range — price=10..50, open at either end. Caffeine also exposes min, max, avg and sum for the facet, computed over the base set.
Range statistics exclude the facet’s own range, for the same reason disjunctive counts do: a slider that collapsed to the range you just chose would be unusable.
Date
Stored as Unix timestamps, so they refine as numbers but format as dates. Labels are rendered through Craft’s formatter in the site’s locale.
Geo
A coordinate pair, filtered by distance from a point rather than by equality. Point the attribute’s path at an address field and the extractor supplies the latitude and longitude.
{# Fixed radii around a point the page already knows #}
{% for km in [5, 10, 25] %}
<a href="{{ search.geoUrl('near', lat, lng, km * 1000) }}">Within {{ km }} km</a>
{% endfor %}
Three things to know:
- Radius is in metres, and the URL carries
near=35.2271,-80.8431,8000. - A radius of zero filters nothing. It exists so a listing can be ordered by distance without also being narrowed — sorting needs a point even when filtering does not.
- Geo facets have no buckets, so there is no refinement list for one. They appear in current refinements as a removable “Within 8 km” chip, and they constrain every other facet’s counts exactly as any other refinement does.
Distance is the haversine great-circle distance, rounded to whole metres and compared as an integer. That rounding is not cosmetic: sin, cos and sqrt are not guaranteed to agree to the last bit between PHP and a JavaScript engine, and a record sitting exactly on the radius could otherwise be inside it on the server and outside it in the browser.
sortBy: "distance" is the only sorting that cannot be precomputed into the artifact, because the point it measures from is chosen by the visitor. Records with no coordinates sort last and never match a radius.
Ordering values
A facet’s values come back by count (descending) by default, or alphabetically, or in an order you set explicitly. Ties break on the value itself, compared by code unit rather than by locale collation — Collator and localeCompare disagree with each other and vary by ICU version, so a server and a browser would otherwise sort the same facet differently, intermittently, on some machines only.
Editions
Lite builds string and boolean facets. Hierarchical, numeric, date and geo need Pro. A Lite site that inherits a Pro definition has the unsupported facets dropped rather than reinterpreted — treating a geo facet as a string facet would give one bucket per record, which is visibly broken and harder to diagnose than a facet that is simply absent.