Search Products
The list endpoint at GET /api/shop/products doubles as the search & filter endpoint. Every storefront facet — keyword search, category, price range, attribute filters, sort, "new" / "featured" — is just a query parameter. This page is the complete reference for those parameters.
Endpoint
GET /api/shop/productsSame URL as Products; the only difference is which query parameters you send.
Reserved parameters
These names are interpreted by the search/sort/pagination layer — never as attribute filters:
| Parameter | Type | Default | Description |
|---|---|---|---|
query | string | — | Search term. Matches against product SKU and product name. |
sort | string | — | Sort token. Either compound (name-asc, price-desc) or a bare key paired with order (sort=name&order=desc). See Sort tokens below. |
order | string | asc | Direction when sort is a bare key. Ignored if sort already has a -asc / -desc suffix. |
page | integer | 1 | 1-based page number. |
per_page | integer | 30 | Items per page. Max 50. |
locale | string | — | Locale override (alternative to the X-Locale header). |
channel | string | — | Channel override (alternative to the X-Channel header). |
filter | string | — | JSON filter object — GraphQL parity. Example: {"color":{"match":"3","match_type":"PARTIAL"}}. Most clients prefer the simpler ?<attribute>=<id> shorthand. |
type | string | — | Product type. One of simple, configurable, bundle, grouped, virtual, downloadable, booking. |
category_id | integer | — | Filter by category ID. categoryId (camelCase) is also accepted. |
price | string | — | Compound price range from,to (e.g. 10,200). |
price_from | number | — | Minimum price (inclusive). Equivalent to the lower bound of price. |
price_to | number | — | Maximum price (inclusive). Equivalent to the upper bound of price. |
new | boolean | — | 1 to restrict to products with the "new" flag set. |
featured | boolean | — | 1 to restrict to products with the "featured" flag set. |
Attribute filters (anything else)
Any query parameter not listed above is treated as a filterable attribute. No schema changes are required when admins add new filterable attributes — the URL just works.
| Pattern | Example | Effect |
|---|---|---|
?<code>=<option_id> | ?color=3 | Single option ID match |
?<code>=id1,id2,… | ?size=4,5,6 | Multi-option (OR) match |
?brand=N, ?material=N | ?material=12 | Same shape — works for any filterable attribute |
?filter[color]=3&filter[size]=5 | (Swagger UI form) | API Platform's bracket-syntax form. Same semantics as the bare-key form. |
To list which attributes are filterable and what their option IDs are, fetch GET /api/shop/attributes and look for isFilterable: 1. Each attribute's options[] array has the IDs.
Sort tokens
The compound form (<key>-<dir>) is recommended:
| Token | Sort by |
|---|---|
name-asc | Product name, A→Z |
name-desc | Product name, Z→A |
price-asc | Price, low→high |
price-desc | Price, high→low |
created_at-asc | Oldest first |
created_at-desc | Newest first |
updated_at-asc | Oldest update first |
updated_at-desc | Most recently updated first |
id-asc | Ascending ID (catalog order) |
id-desc | Descending ID |
Alternative form: ?sort=name&order=desc. Pick one; mixing them isn't useful.
Combining filters
All filters are AND-combined. There is no OR across different filters — the only multi-value form is comma-separated values within a single attribute (?size=4,5,6).
GET /api/shop/products?
category_id=5
&color=3
&size=4,5
&price=10,200
&new=1
&sort=price-desc
&per_page=24Response
200 OK — JSON array of card-level product objects (same shape as Products). Pagination headers always emitted.
An empty filter result returns an empty array
[]with200 OKandX-Total-Count: 0. It does not 404.
Common pitfalls
- Sending
itemsPerPageinstead ofper_page— the legacy API Platform name is not accepted. - Filtering by an attribute that isn't flagged
isFilterable=1— the parameter is silently ignored. - Filtering by an attribute code but passing an option label (e.g.
?color=Red) — values are matched against option IDs, not labels. - Combining
price+price_from/price_to— pick one. The compound form wins if both are present.
Related Resources
- Products — same endpoint with no filters
- Single Product — fetch one product after the user picks a result
- Categories — discover category IDs for
?category_id=N - Attributes — discover filterable attribute codes
- Pagination

