# Category pages

Browse a category without words, with its products, its subcategories and the banners placed on it.

A category page is a search without text on the `category_page` surface, naming the category. The sofa page `wohnen/sofas` (wohnen: living) lists four sofas:

```sh title="Terminal"
curl -s 'http://127.0.0.1:7800/v1/search?on=category_page&category=wohnen/sofas' \
  | jq -c '.listed, (.sections[] | {type, total, hits: [.hits[].entity]})'
```

```json title="Answer: 200 · 13 ms · release b25a6aaa"
"product"
{"type":"product","total":4,"hits":["product:SOFA-ASKA-2","product:SOFA-LUND-3","product:SOFA-NIKO-SCHLAF","product:SOFA-VIDAR-ECK"]}
```

The answer holds one [section](/docs/reference/glossary#section), the type the page lists, with the page's [facets](/docs/reference/glossary#facet). `listed` names that type: products, unless the category holds more of another type with offers, such as a workshop's services. Filters and pages work as in any search ([Facets and filters](/docs/facets)).

**Reference:** [`category`](/docs/reference/query-api#search.category) · [`listed`](/docs/reference/query-api#search.answer.listed)

## The category and below it

A page lists what the [catalog](/docs/reference/glossary#catalog) assigns to its category and to every category below it. `wohnen` lists the products of its five subcategories, and its `category` facet counts them:

```sh title="Terminal"
curl -s 'http://127.0.0.1:7800/v1/search?on=category_page&category=wohnen' \
  | jq -c '.sections[] | select(.type == "product")
      | .total, (.facets[] | select(.field == "category") | .values[] | {value, label, count})'
```

```json title="Answer: 200 · 12 ms · release b25a6aaa"
11
{"value":"wohnen/sofas","label":"Sofas","count":4}
{"value":"wohnen/tische","label":"Tische","count":3}
{"value":"wohnen/sessel","label":"Sessel","count":2}
{"value":"wohnen/regale","label":"Regale","count":1}
{"value":"wohnen/stuehle","label":"Stühle","count":1}
```

The category facet is `hierarchical`: it lists the subcategories one level down, such as Sessel (armchairs) and Tische (tables). Choosing one, as `{ "category": { "within": "wohnen/sofas" } }`, narrows this page. Link it to its own page instead where the shopper should get that page's own facets and banners.

<Callout type="info">
    A category's `where` in `categories.json` is checked but not applied: a page lists only what the catalog assigns to
    it and below it.
</Callout>

## Banners and teasers

`placements` holds the banners [rules](/docs/reference/glossary#rule) place on the page, in the order the page draws them. [Banners and teasers](/docs/banners) shows how a merchant places one:

```sh title="Terminal"
curl -s 'http://127.0.0.1:7800/v1/search?on=category_page&category=wohnen/sofas' \
  | jq -c '.placements[] | del(.text, .image, .link)'
```

```json title="Answer: 200 · 8.0 ms · release b25a6aaa"
{"slot":"top","kind":"banner","title":"Sofas im Angebot","rule":"home/sofa-sale"}
{"slot":"grid","position":4,"kind":"teaser","title":"Sessel aus Leder, Samt und Stoff","rule":"home/sofa-sale"}
```

The home store's rule `home/sofa-sale` puts a banner on `top`, "Sofas im Angebot" (sofas on sale), and a teaser to the armchairs in the `grid` before the fourth tile. Each comes with its text, image and link in the request's [locale](/docs/reference/glossary#locale).

A banner is not a hit: totals, facets and hits are the same without it, so paging stays as it is. A grid banner's `position` counts tiles across pages:

```sh title="Terminal"
curl -s 'http://127.0.0.1:7800/v1/search?on=category_page&category=wohnen/sofas&per_page=2&page=2' \
  | jq -c '[.placements[] | {slot, position}], (.sections[] | {total, hits: [.hits[].entity]})'
```

```json title="Answer: 200 · 7.2 ms · release b25a6aaa"
[{"slot":"grid","position":4}]
{"total":4,"hits":["product:SOFA-NIKO-SCHLAF","product:SOFA-VIDAR-ECK"]}
```

With two tiles per page, the teaser stands before the second tile of page 2, at `position - (page - 1) * per_page`. A top banner comes with the first page only, a bottom one with the last. A `count_only` request carries none.

**Reference:** [`placements`](/docs/reference/query-api#search.answer.placements) · [`position`](/docs/reference/query-api#search.answer.placements.position)

## Where the page starts sorting

A page without words starts in the default sort of its category's [node](/docs/reference/glossary#node), the nearest up the tree, and the answer's `sorts` marks it. The home store's pages start by relevance, labeled "Beste Treffer" (best matches). [Sorting](/docs/sorting) shows how to offer the others.

## An unknown category

A category the catalog does not hold answers an empty product section, not an error. A storefront that serves pages by their URL learns of it earlier: [resolve](/docs/urls#resolve-a-url) answers its path with `not_found`.

## Breadcrumbs and the page's URL

The page's path comes from the catalog, per locale, and resolve answers it with the trail above the page:

```sh title="Terminal"
curl -s -G http://127.0.0.1:7800/v1/resolve --data-urlencode 'url=/wohnen/sofas' -d results=0 \
  | jq -c '{entity, canonical}, .breadcrumbs'
```

```json title="Answer: 200 · 2.4 ms · release b25a6aaa"
{"entity":"category:wohnen/sofas","canonical":"/wohnen/sofas"}
[{"title":"Wohnen","url":"/wohnen"},{"title":"Sofas","url":"/wohnen/sofas"}]
```

The same page sits at `/en/living/sofas` in English. Without `results=0`, resolve brings the page's results in the same call, so a storefront renders a category page from one request ([URLs and redirects](/docs/urls)).

**Reference:** [`breadcrumbs`](/docs/reference/query-api#resolve.answer.breadcrumbs) · [`canonical`](/docs/reference/query-api#resolve.answer.canonical)

## Next

- [Facets and filters](/docs/facets): the facets a page answers with and how a shopper chooses.
- [Sorting](/docs/sorting): the orders a page offers.
- [URLs and redirects](/docs/urls): serve every page from its own URL.
