--- title: Search results description: Configure Luma's search results page — which URLs it takes over, where it renders, facets, sorting, flags, "show more", and the keys shared with category pages. slug: luma/widgets/search docKind: reference hub: luigisbox-ai --- The search widget takes over your existing search results page. It renders into an element of your page, hides your own listing while its own is shown, and leaves the rest of the page — header, breadcrumbs, footer — as it is. Your search form keeps submitting to the same URL. ## Configuration `integration/search/_config.ts`: ```ts export default { urlMatch: '^/search$', queryParam: 'q', anchor: '#search-results', position: 'prepend', surface: 'lbs_search_main', size: 24, facets: ['brand', 'color', 'lbx:price'], sorts: ['', 'lbx:price:asc', 'lbx:price:desc'], fields: ['@title', 'url', 'image_url', 'price', 'brand'], quick: [{ type: 'category', size: 4 }], }; ``` ### Which pages | Key | Default | | |---|---|---| | `urlMatch` | Any page with `queryParam` | A regular expression on the URL's path | | `queryParam` | — | The query parameter holding the search query. Without it, the first capture group of `urlMatch` is the query: `^/search/([^/]+)$`. | For URLs a regular expression cannot describe, set functions in `search/_init.ts`: | Hook | | |---|---| | `active(url)` | Whether this URL is a search page | | `readQuery(url)` | The query on this URL | A search page without a query is left to your site. ### Search-only keys | Key | Default | | |---|---|---| | `quick` | — | Other object types shown above the results, such as category or brand tiles. One request per entry: `{ "type", "size", "fields" }`. | | `inPlaceQuery` | `true` | A new query from your search form while on the search page renders in place, with a new history entry and no page load. Set `false` when parts of your search page outside the listing are server-rendered and must follow the query, or when your page-view analytics must fire per search. | | `prefetchWhileTyping` | `true` | Once the phrase typed in your search input settles, its results are fetched, so Enter shows them at once | | `prefetchDelayMs` | `300` | How long the phrase must stay unchanged first | | `prefetchMaxAgeMs` | `120000` | How long a result fetched while typing is used | | `prerenderWhileTyping` | `false` | In Chrome and Edge, also prerender the search page of the settled phrase, so Enter from another page shows a page already rendered. Each settled phrase costs your server a page render, and your analytics count a page view unless they wait for the prerendered page to be shown. | | `prerenderEagerness` | `immediate` | `immediate` or `eager` | ## Listing keys Search results and [category pages](/luma/widgets/category-pages/) are the same listing and share these keys. ### Placement | Key | Default | | |---|---|---| | `anchor` | — | Required. A CSS selector of the element the listing is placed relative to, or a list of candidates, most specific first | | `position` | — | Required. `prepend` or `append` inside the anchor, `before` or `after` it | | `rootClass` | — | Classes of yours put on the listing's element, so it sits in your own grid | | `settleMs` | — | Wait until the anchor stops changing for this long — for frameworks that re-render after load | | `reserveSelector` | The anchor | The element that reserves the listing's remembered height while it loads, so the page does not jump | | `holdTimeoutMs` | `4000` | If the listing has not rendered by then, your own is shown instead | | `showTitle` | — | Show the query, or the category name, as a heading | ### What is requested | Key | Default | | |---|---|---| | `surface` | — | Required. A `search` surface for search, a `collection` surface for category pages | | `size` | — | Required. Results per page: the first page, and each "show more" | | `fields` | The surface's | [Fields](/discovery/fields/) the tiles need. Fewer fields, smaller responses. | | `baseFilters` | — | A [filter expression](/discovery/filters/) always applied, combined with the shopper's filters | ### Facets, sorting, flags | Key | Default | | |---|---|---| | `facets` | — | Required. Fields offered as filters, in sidebar order. Numeric fields become range sliders. See [Facets](/discovery/facets/). | | `facetSize` | `50` | Values per text facet | | `emptyValues` | `false` | With a filter applied, also list the values the unfiltered page has, marked `empty`, for sites that grey them out. Costs one more request per query or category. | | `facetGroups` | All open | `open`: which groups start expanded — `true`, `false`, or the first N, optionally per viewport min-width (`{ "0": false, "1024": 3 }`). `search`: facets whose group gets a search box over its values. `searchMin` (`10`): the fewest values that get one. | | `rangeSteps` | `1` | The step of a range slider, by facet: `{ "lbx:price": 10 }` | | `sorts` | — | Required. Sort options as `field:direction`; `''` is relevance. Labels come from the translations `search.sorts`. | | `flags` | — | On/off filters of your own: `[{ "id": "in-stock", "filter": { "in_stock": { "eq": true } } }]`. Labels come from `search.flags.`. | | `facetsApply` | `instant` | How changes inside the filter drawer apply: `instant`, or `staged` until its apply button is pressed. Per viewport min-width too: `{ "0": "staged", "768": "instant" }`. | The filter drawer is your markup: `data-lbx-drawer="open"` on the control that opens it, `data-lbx-drawer-panel` on the panel, `data-lbx-drawer="apply"` and `data-lbx-drawer="close"` on its buttons. Facet values carry no counts. ### Behaviour | Key | Default | | |---|---|---| | `restoreOnBack` | `true` | Back from a product restores the listing — every page loaded — at the tile the shopper followed, then refreshes it in the background | | `prefetchNextPage` | `true` | The next page is fetched as the shopper nears the end, so "show more" appends at once | | `contentVisibility` | `false` | Tiles out of view skip style, layout and paint, which roughly halves a long list's re-render. Leave it off when a tile draws outside its box — a hover panel, a badge hanging over the edge — or contains a `position: fixed` element. | Set in `_init.ts` or `_widget.ts`: | Hook | | |---|---| | `onRender({ route, query, total, shown, page, url })` | After each render — to keep your document title in step, for example | ## The URL The listing's state is in the URL, so a filtered page can be shared, bookmarked and reached with Back. Luma's own parameters start with `lbx.`: `lbx.sort`, `lbx.flag`, and `lbx.` for each facet. "Show more" does not change the URL, and a listing always opens on its first page. ## See also - [Category pages](/luma/widgets/category-pages/) — the same listing for collections - [Templates](/luma/templates/) — `search-*` templates and their scope - [Behaviour and fallbacks](/luma/behaviour/) — when your own listing is shown instead