--- title: Facets description: Build a filter sidebar with POST /discovery/v1/facets — terms, ranges and hierarchies, computed over a result set you already have. slug: discovery/facets docKind: guide hub: luigisbox-ai --- Facets are the filter sidebar: the brands present in these results, the price range they span, the category tree they sit in. `POST /discovery/v1/facets` computes them for a result set you already fetched, rather than re-running the query. The request takes a `guid` rather than a query, so facets and results always describe the same result set. ## Request ```bash curl -X POST 'https://api.eu1.luigisbox.ai/discovery/v1/facets?channel_id=lbn_4hj9tv' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "guid": "3ab549eb64a20a0c", "facets": [ { "field": "brand", "size": 20 }, { "field": "color" }, { "field": "price" } ] }' ``` | Query parameter | Required | Notes | |---|---|---| | `channel_id` | Yes | Serving destination | | Body field | Required | Notes | |---|---|---| | `guid` | Yes | From a previous `/search` or `/collections` response | | `facets` | Yes | 1–100 entries of `{ "field": …, "size": … }` | | `hierarchy_anchors` | No | Which branches of a hierarchy to expand. Up to 50. | | `hierarchy_depths` | No | How many child levels to expand, per field. Up to 50. | There is no `surface_id`: the `guid` already identifies the result set, and with it the surface that produced it. A token authorized for either search or collections may call this endpoint. ### Capping values `size` limits how many values a field returns: ```json { "field": "brand", "size": 20 } ``` Omit it for the surface's default. Cap long-tail fields such as brand. ## Response Facets come back keyed by field name, each tagged with its `type`: ```json { "guid": "3ab549eb64a20a0c", "facets": { "brand": { "type": "terms", "field": "brand", "values": ["northwear", "atlas", "kestrel"] }, "price": { "type": "range", "field": "price", "min": 12.5, "max": 249.0 }, "lbx:category_hierarchy": { "type": "hierarchy", "field": "lbx:category_hierarchy", "values": [ { "value": "category/clothing", "title": "Clothing", "has_children": true, "children": [] } ] } } } ``` Switch on `type` to pick a control: | `type` | Fields | Render as | |---|---|---| | `terms` | `values` — the distinct values present | Checkboxes or a multi-select | | `range` | `min`, `max` — the bounds in these results | A slider or two number inputs | | `hierarchy` | `values` — a tree of nodes | An expandable tree | ```js function renderFacet(name, facet) { switch (facet.type) { case 'terms': return checkboxes(name, facet.values); case 'range': return slider(name, facet.min, facet.max); case 'hierarchy': return tree(name, facet.values); } } ``` ## Hierarchy facets Which fields a surface can facet hierarchically is part of its configuration — ask your Luigi's Box contact to set one up. A hierarchy node's `value` is its **fully expanded path**, with segments joined by ` > `: ```text category/clothing > category/shirts ``` `title` is the node's own display name, and `has_children` tells you whether it can be expanded. By default only the roots come back, with `children` empty. ### Expanding Two fields open the tree. `hierarchy_depths` expands from the roots downwards: ```json { "hierarchy_depths": [ { "field": "lbx:category_hierarchy", "depth": 2 } ] } ``` `depth` must be between 1 and 5, and each field may appear at most once. Anything else is a `422`. `hierarchy_anchors` opens one specific branch, naming the full node path: ```json { "hierarchy_anchors": [ { "field": "lbx:category_hierarchy", "path": "category/clothing > category/shirts" } ] } ``` List several entries to open several branches, across several fields. The path is a JSON string, so the ` > ` separator needs no encoding. ```js const body = { guid, facets: [{ field: 'lbx:category_hierarchy' }], hierarchy_anchors: openBranches.map((path) => ({ field: 'lbx:category_hierarchy', path })), }; ``` Hold the set of open branches in your UI state and send it whole on each request, so the tree renders in the state the shopper left it in. ## The loop Facets and results move together. Each new filter combination is a new result set: 1. `POST /search` → `hits`, `guid`. 2. `POST /facets` with that `guid` → the sidebar. 3. Shopper checks *northwear* → `POST /search` again with `"filters": { "brand": { "in": ["northwear"] } }` → new `hits`, **new `guid`**. 4. `POST /facets` with the new `guid` → the sidebar, now narrowed to what is still reachable. Never reuse a `guid` after changing `filters`. The facets you get back would describe the previous result set, and the sidebar would offer values that no longer match anything. Calling `/facets` also computes an exact count, so a `total` that came back `null` from the search becomes a real number. ## Choosing what to facet - **Facet on what a shopper filters by**, not on everything you index. Brand, price, colour, size, availability. - **Cap the long tail** with `size`, for example `{ "field": "brand", "size": 20 }`. - **Numeric fields need to be numeric.** A `price` indexed as text facets as a terms list of strings, not a range. Check with [catalog metadata](/indexing/catalog-metadata/) and fix it in the [mapping](/indexing/mapping/). ## See also - [Filters](/discovery/filters/) — turning selections into a filter - [Search](/discovery/search/) · [Collections](/discovery/collections/) - [Catalog metadata](/indexing/catalog-metadata/) — which fields are facetable, and their types