# Add search and filters Build a product search whose URL can be refreshed or shared without losing the selected filters. Use one validated search expression with the same commerce context as the product grid. ## Before you begin Complete [Build product listing and detail pages](/guides/storefront/product-pages). Read the supported filter table for [Storefront products](/api/storefront-graphql/queries/products#query). Use [facets](/guides/storefront/facets) when the configured catalog should determine filter labels and values. ## 1. Request results and facets together Storefront API ```graphql query SearchCatalog($query: String, $storeId: ID!, $priceChannelId: ID!, $currency: String!, $country: String!, $after: String) { products(first: 20, after: $after, query: $query, storeId: $storeId, priceChannelId: $priceChannelId, priceCurrency: $currency, priceCountry: $country, sortKey: NAME, sortDirection: ASC) { nodes { id name slug } facets { name field queryField values { name count } } pageInfo { hasNextPage endCursor } } } ``` Variables ```json { "query": "tag:lighting AND available_for_sale:true", "storeId": "", "priceChannelId": "", "currency": "EUR", "country": "DK", "after": null } ``` The example filters tagged, available products using documented product filters. For free text, start with a simple term and add field clauses deliberately. See [Search query language](/concepts/query-language) for quoting and composition. ## 2. Make URL state explicit Store the search term, selected facet values, and sort in URL parameters. Resolve those parameters into supported filter fields and values on the server. Keep selected values separate from the final expression so the UI can remove one filter without parsing its own query string. Group alternatives within one filter, then combine independent filters with `AND`. Quote and escape values with whitespace or search punctuation. Do not accept arbitrary field names directly from the URL. ## 3. Reset pagination when inputs change A new term, filter, sort, locale, or market starts at the first page. Discard the old cursor. When requests overlap while someone types, cancel the older request or ignore its response so it cannot replace newer results. ## Verify Test a matching term, a multi-word value, two selected values in one facet, filters across two facets, an empty result, and another cursor page. Refresh the URL and confirm the same selection appears. Use the returned price-facet amounts only with their original currency context.