--- title: Category pages description: Configure Luma's category listings — which pages it takes over, how a page's URL becomes a category, mapping your own URL parameters, and per-category facets. slug: luma/widgets/category-pages docKind: reference hub: luigisbox-ai --- The category page widget takes over the product listing on your category pages: the grid, its filters and its sorting. Breadcrumbs, the heading, subcategory tiles and banners stay yours. It is the same listing as [search results](/luma/widgets/search/), served from a `collection` [surface](/concepts/domain-model/#surfaces-and-widgets). Its folder is `integration/plp/`. ## Configuration `integration/plp/_config.ts`: ```ts export default { urlMatch: '^/c/', surface: 'lbs_collection_categories', lookup: { field: 'url' }, params: { page: 'page', sort: { param: 'order', values: { 'price-asc': 'lbx:price:asc', 'price-desc': 'lbx:price:desc' } }, facetParams: { brand: { param: 'brand' } }, }, }; ``` | Key | Default | | |---|---|---| | `urlMatch` | — | Required. A regular expression on the URL's path, matching your category pages | | `surface` | — | Required. A `collection` surface | | `lookup` | — | How a page's URL becomes a category — see below | | `params` | — | Your own URL parameters, mapped onto the listing — see below | | `earlyHold` | `true` | `false` when `urlMatch` also matches pages that are not category pages and `style-host.css` would hide something on them. The [early-hold snippet](/luma/installing/#the-early-hold-snippet) matches on `urlMatch` alone. | Every [listing key](/luma/widgets/search/#listing-keys) can be set here. Those left unset are taken from `search/_config.ts`: `anchor`, `position`, `settleMs`, `reserveSelector`, `holdTimeoutMs`, `size`, `facets`, `facetSize`, `emptyValues`, `sorts`, `fields`, `baseFilters`, `rootClass`, `restoreOnBack`, `prefetchNextPage`, `facetsApply` and `facetGroups`. So are templates and `style-host.css`, when `plp/` has none of its own. Sort labels come from the translations `plp.sorts`, and the built-in label for `''` is "Recommended". ## From URL to category Luma needs, for each page, a [filter expression](/discovery/filters/) that selects the category's products, and a title. Two ways to get them: **Look up the category record.** `lookup` finds the catalog object of type `category` whose `field` equals the page's path, without a trailing slash. `absolute: true` compares the origin and path instead; `type` names another object type. Then `toFilter`, set in `plp/_widget.ts`, turns that record into the filter: ```ts // integration/plp/_widget.ts import type { PlpConfigType } from '@/types'; export default (c: PlpConfigType) => { c.toFilter = (category) => ({ scope: { category_path: { eq: category.path } }, title: category['@title'], }); }; ``` **Read it from the URL.** `fromUrl(url)` returns the filter and title directly, with no lookup. Either may return `null`: the page is left to your site. A category with no products is left to your site too. Narrow `urlMatch` further with `active(url)` in `plp/_init.ts`. ## Your URL parameters A category page's state lives in your own parameters, so your existing links, filters and canonical URLs keep working. `params` maps them: | Key | | |---|---| | `page` | Your page parameter. Luma shows only the first page and appends the rest with "show more", so your page 1 — or no page parameter — is Luma's, and a later page is left to your own listing. Luma never writes it. | | `sort` | `{ "param", "values" }`: your sort parameter and a map of your value to Luma's `field:direction` | | `flags` | On/off parameters with a fixed value: `[{ "param": "stock", "value": "1", "filter": { … } }]`. Labels come from `plp.flags["stock=1"]`. | | `facetParams` | Facet field → `{ "param", "values", "slug" }`. `values` maps your values to catalog values; a value mapped to `''` cannot be expressed, and its URL is left to your site. `slug: false` passes values through unchanged. With neither, your value is a slug of the catalog value. | | `separator` | The separator between several values in one parameter | | `ranges` | `[{ "min", "max", "field" }]`: two parameters read as a range filter on `field` | | `ignore` | A regular expression of parameters that never stop a takeover, such as tracking parameters | A URL with a parameter Luma cannot express is left to your own listing, so no filter is silently dropped. A facet value your parameters cannot carry is written as `lbx.`. ## Facets per category When the facets depend on the category — its own technical parameters, for example — set `facetsFor(url, category)` in `plp/_widget.ts`. It returns `{ facets, layout }`: the fields to facet, in order, and optionally the sidebar's group order including keys of your own. It may return a promise. `null` uses `facets`. ## See also - [Search results](/luma/widgets/search/) — every listing key - [Collection](/discovery/collection/) — the request a category page sends