# Pages and their filters

Choose the filters and sorting of each page, so the sofa page offers width and seats and the lamp page the light source.

Shoppers on the sofa page narrow by width and seats, on the lamp page by the light source. `categories.json` lays out each page's [facets](/docs/reference/glossary#facet) and sorting: `default` for every page, `nodes` for the categories that differ.

```json title="tests/fixtures/home/categories.json (trimmed)"
{
    "default": {
        "facets": ["category", { "field": "price", "display": "histogram" }, "style", "on_sale", "availability"]
    },
    "nodes": [
        {
            "category": "wohnen/sofas",
            "facets": [
                "width",
                { "field": "seats", "kind": "list", "display": "buttons" },
                "color",
                "upholstery",
                "price"
            ]
        },
        { "category": "leuchten", "facets": ["room", "light_source", "material", "color", "price"] }
    ]
}
```

A [node](/docs/reference/glossary#node) names a category of the [catalog](/docs/reference/glossary#catalog), such as the sofas `wohnen/sofas` (wohnen: living) or the lamps `leuchten`. Its list replaces the one the page would inherit, whole and in its order.

<Callout type="warn">
    A node's `where`, a smart category such as `{ "on_sale": true }`, is checked when the
    [release](/docs/reference/glossary#release) is stored but not applied: a page lists only what the catalog assigns to it
    and below it.
</Callout>

**Reference:** [`default`](/docs/reference/release-files/categories#default.facets) · [`nodes`](/docs/reference/release-files/categories#nodes.facets) · [`where`](/docs/reference/release-files/categories#nodes.where)

## Where a page's settings come from

A page takes its facets from the nearest node up the tree that lists some, else from `default`. The [management API](/docs/reference/glossary#management-api) says which, for any page:

```sh title="Terminal"
curl -s -H "Authorization: Bearer $key" -H 'Content-Type: application/json' \
  -d '{"category": "wohnen/sofas"}' $api/pages/settings | jq -c '.facets | .origin, [.facets[].field]'
curl -s -H "Authorization: Bearer $key" -H 'Content-Type: application/json' \
  -d '{"category": "leuchten/stehleuchten"}' $api/pages/settings | jq -c '.facets | .origin, [.facets[].field]'
```

```json title="Answer: 200 · 5.5 ms · release b25a6aaa"
{"from":"page"}
["width","seats","color","upholstery","shape","features","seat_height","price","delivery_days","brand"]
{"from":"category","category":"leuchten","title":"Leuchten"}
["room","light_source","color_temperature","features","material","color","price","energy_class"]
```

The sofa page sets its own. The floor lamps (Stehleuchten) have no node, so they take those of the lamps (Leuchten) above them. A page with no node above it takes `default`, and so does the search page, unless its query names a category.

Groups, the default sort and the sort options are inherited the same way, each on its own: a node that sets only its sort keeps the facets above it.

**Reference:** [`POST /api/v1/pages/settings`](/docs/reference/management-api#show-page-settings) · [`origin`](/docs/reference/management-api#show-page-settings.answer.facets.origin)

## How a facet is shown

A facet is a field code, or an object that says how to count and draw it:

```json title="tests/fixtures/home/categories.json (trimmed)"
[
    { "field": "seats", "kind": "list", "display": "buttons" },
    { "field": "price", "display": "histogram" },
    {
        "field": "color_temperature",
        "kind": "buckets",
        "buckets": [
            { "to": 3300, "label": { "de": "Warmweiß", "en": "Warm white" } },
            { "from": 3300, "to": 5300, "label": { "de": "Neutralweiß", "en": "Neutral white" } },
            { "from": 5300, "label": { "de": "Tageslichtweiß", "en": "Daylight" } }
        ]
    }
]
```

`kind` decides what is counted: a `list`, `swatch`es, a `range`, `buckets`, a `toggle` or the `hierarchical` category tree. `display` decides how the storefront draws it, such as the seats as `buttons`. The lamps count color temperature in three buckets, and the price is a range drawn as a [histogram](/docs/facets#the-price-histogram), which no other field has.

A field code alone is shown as `default` shows it, else in its natural kind: a list for options, a range for numbers. The settings answer marks such a facet `shown_as_default`.

**Reference:** [`kind`](/docs/reference/release-files/categories#nodes.facets.kind) · [`display`](/docs/reference/release-files/categories#nodes.facets.display) · [`buckets`](/docs/reference/release-files/categories#nodes.facets.buckets) · [every key of a facet](/docs/reference/release-files/categories#nodes.facets) · [`shown_as_default`](/docs/reference/management-api#show-page-settings.answer.facets.facets.shown_as_default)

## Groups

A group draws several facets of a page under one heading. The home store has none, but the sofa page could gather width and seat height under "Maße" (measures):

```json title="categories.json, the sofa node (trimmed)"
{
    "category": "wohnen/sofas",
    "groups": [{ "group": "measures", "label": { "de": "Maße", "en": "Measures" }, "facets": ["width", "seat_height"] }]
}
```

A group stands where its first facet stands, and its facets keep their own settings. With `"display": "finder"`, they stand side by side as selects, so each must be shown as `select`. The panel shows a group as one row; you write groups in `categories.json`.

**Reference:** [`groups`](/docs/reference/release-files/categories#nodes.groups) · [`finder`](/docs/reference/release-files/categories#nodes.groups.display.finder)

## Sorting

`sort.default` sets the order a page without words starts in, and `sort.options` the orders shoppers can choose:

```json title="categories.json, the sofa node (trimmed)"
{
    "category": "wohnen/sofas",
    "sort": { "default": "price_asc", "options": ["relevance", "price_asc", "price_desc"] }
}
```

The options are `relevance` and the sorts of `ranking.json`. A search with words starts by relevance, and the shopper's own choice wins over both. The home store starts every page by relevance; [Sorting](/docs/sorting) shows the storefront's side.

**Reference:** [`sort`](/docs/reference/release-files/categories#nodes.sort) · [`default.sort`](/docs/reference/release-files/categories#default.sort)

## The grid

A category page's first places can hold products the merchant chooses: the page's grid. It is a [rule](/docs/reference/glossary#rule) that [pins](/docs/reference/glossary#pin) products while nothing is typed:

```json title="rules.json"
{
    "id": "grid-of-sofas",
    "description": "Grid of Sofas",
    "when": { "category": { "is": "wohnen/sofas" }, "query": { "empty": true } },
    "then": {
        "pin": [
            { "entity": "product:SOFA-NIKO-SCHLAF", "position": 1 },
            { "entity": "product:SOFA-VIDAR-ECK", "position": 2 }
        ]
    }
}
```

The pins hold within the shopper's filters, and the page's own order follows them. A grid pins up to 100 places. The panel arranges it on the page, and [Rules](/docs/rules) lists it among the other rules.

**Reference:** [`then.pin`](/docs/reference/release-files/rules#then.pin) · [`grid`](/docs/reference/management-api#ListedRule.grid)

## What the run derives

Write only what differs. Whatever the release leaves out of `categories.json` is derived from the catalog on every [index run](/docs/reference/glossary#index-run):

```json title="categories.json"
{ "nodes": [{ "category": "wohnen/sofas", "facets": ["width", "seats", "color", "price"] }] }
```

From this file, the run derives the `default` facets from what the products carry, and the sorting from relevance and the sorts of `ranking.json`. It adds a node for each category of 24 products or more whose products carry other filters than it would inherit. The sofa node stays as written.

A [derived setting](/docs/reference/glossary#derived-setting) says `derived: true` in the settings answer, with the run's reason, and the panel marks it **Chosen for you**.

**Reference:** [`derived`](/docs/reference/management-api#show-page-settings.answer.facets.origin.page.derived) · [`reason`](/docs/reference/management-api#show-page-settings.answer.facets.facets.reason)

## Change a page

A change waits in a [draft](/docs/reference/glossary#draft) until you publish it, whether you make it in the [panel](/docs/reference/glossary#panel) or through the API.

**In the panel:**

**Merchandising → Pages** shows the shop's pages as a tree, the **Search page** on top. Choose **Sofas**: its **Filters** and **Sorting** say where they come from, **Set here**, **From Leuchten**, **From all pages** or **Chosen for you**.

![The Pages screen with Sofas chosen in the tree, and its filters, Breite first, marked Set here.](/docs/shots/pages-light.avif)

Panel · Merchandising → Pages

Click **Start a draft**, then change the page:

- **Filters:** drag a filter, or press ↑ and ↓ on its handle. **Add a filter** adds one, and its pencil opens **Kind**, **Display** and the rest, each **Automatic** until you choose.
- **Sorting:** **Starts with** sets the default sort, and **Shoppers can choose** lists the options in their order.
- **Grid:** **Pin a product** puts it on the page's first places, and the tiles reorder like the filters.

On a page that inherits, **Write my own** takes a card's settings over, and **Reset** hands them back.

**Preview this page** shows the page as shoppers will see it, with the draft's filters and sorting over the live products; the draft's pins show once it is live. **Review and publish** opens the draft's review, which publishes it.

**Through the API:**

Start a draft from the home store's export, and put the sale toggle first on the sofa page:

```sh title="Terminal"
draft=$(curl -s -X POST -H "Authorization: Bearer $key" -H 'Content-Type: application/x-ndjson' \
  --data-binary @tests/fixtures/home/catalog.jsonl "$api/imports?name=catalog.jsonl" | jq -r .import.id)
version=$(curl -s -H "Authorization: Bearer $key" $api/imports/$draft | jq -r .version)
jq --arg version "$version" '{version: $version, files: {"categories.json":
      ((.nodes[] | select(.category == "wohnen/sofas") | .facets) |= ["on_sale"] + .)}}' \
    tests/fixtures/home/categories.json \
  | curl -s -X PATCH -H "Authorization: Bearer $key" -H 'Content-Type: application/json' -d @- $api/imports/$draft \
  | jq -c '.files | keys'
```

```json title="Answer: 200 · 37 ms"
["categories.json"]
```

The draft now holds a `categories.json` of its own. Send its files with the page to see the sofa page as the draft lays it out:

```sh title="Terminal"
curl -s -H "Authorization: Bearer $key" $api/imports/$draft \
  | jq '{category: "wohnen/sofas", files}' \
  | curl -s -H "Authorization: Bearer $key" -H 'Content-Type: application/json' -d @- $api/pages/settings \
  | jq -c '[.facets.facets[].field]'
```

```json title="Answer: 200 · 28 ms"
["on_sale","width","seats","color","upholstery","shape","features","seat_height","price","delivery_days","brand"]
```

Ask how publishing it would go live:

```sh title="Terminal"
curl -s -H "Authorization: Bearer $key" $api/imports/$draft/publication | jq -r .course.sentence
```

```text title="Answer: 200 · 27 ms"
Goes live at once: only categories.json changed what the Query API reads.
```

Publishing takes the draft live:

```sh title="Terminal"
curl -s -X POST -H "Authorization: Bearer $key" $api/imports/$draft/publish | jq -r .next
```

Here, discard it instead:

```sh title="Terminal"
curl -s -X DELETE -H "Authorization: Bearer $key" $api/imports/$draft | jq -r .state
```

```text title="Answer: 200 · 18 ms"
discarded
```

## How a change goes live

Most changes to a page go live at once. A change to what the release counts rebuilds the search, such as buckets for the seat height, which no page counts yet:

```json title="A facet of the sofa node"
{ "field": "seat_height", "kind": "buckets", "buckets": [{ "to": 45 }, { "from": 45 }] }
```

The order of facets, labels, groups, the sorting and every display but the histogram change only what the [Query API](/docs/reference/glossary#query-api) reads, so publishing them is a switch.

A field, kind, `show` or set of buckets no other page counts changes what the documents hold. So does a price histogram where none was, or removing the last facet over a field. Publishing those rebuilds the search, while the shop answers from the live release.

The draft's publication and **Preview this page** say which way a draft goes live; [Releases](/docs/releases) has the rest.

**Reference:** [`GET .../publication`](/docs/reference/management-api#show-import-publication)

## Next

- [Facets and filters](/docs/facets): read the facets in an answer and send the shopper's choices.
- [Banners and teasers](/docs/banners): put a campaign on the sofa page.
- [Releases](/docs/releases): draft, preview, publish and roll back.
