Get Products
About
The products query returns a cursor-paginated list of catalog products with filtering and sorting. It is the canonical reference for both — Search Products is the same field with its query argument supplied, and uses the identical filter keys and sort keys documented below. Use it to:
- Build product catalog browsing interfaces
- Drive listing, filtering, and sorting experiences
- Create product recommendation rails
- Sync product data with external systems
Each product node carries its basic information (name, SKU, descriptions), pricing, images, categories and custom attributes, availability status, and timestamps.
Wishlist & Compare Flags
Every product carries two per-customer flags so the wishlist and compare icons can be rendered straight from the catalog response. The wishlist and compare endpoints paginate independently of the catalog, so matching those lists against catalog rows on the client is unreliable.
| Field | Description |
|---|---|
isInWishlist | "1" when the product is in the signed-in customer's wishlist for the active channel, "0" when it is not. |
isInCompare | "1" when the product is in the signed-in customer's compare list, "0" when it is not. |
Both need the customer Bearer token and are always "0" for guests. GraphQL returns them as the strings "1" / "0"; the REST API returns the same flags as the integers 1 / 0.
Arguments
| Argument | Type | Description |
|---|---|---|
first | Int | The number of products to return per page. Used for forward pagination. Default: 30 |
after | String | The cursor of the product to start after. Used with first for pagination. |
last | Int | The number of products to return in reverse. Used for backward pagination. Default: 30 |
before | String | The cursor to start before. Used with last for reverse pagination. |
sortKey | String | Field to sort by: ID, TITLE (alias NAME), PRICE, CREATED_AT, UPDATED_AT. Case-insensitive. Default: ID |
reverse | Boolean | Reverse the sort order. Default: false |
query | String | Search query string for filtering products. Supports advanced search syntax. |
filter | String | JSON string of filter keys (see below). Pass as a single-line JSON string with escaped quotes. |
Filter keys
The filter argument is a JSON object encoded as a string. Accepted keys:
| Key | Type | Description |
|---|---|---|
type | String | Product type: simple, configurable, bundle, grouped, virtual, downloadable, booking. |
sku | String | Exact SKU match. |
category_id | Int | Restrict to a category. |
price_from | Number | Minimum price (inclusive). |
price_to | Number | Maximum price (inclusive). |
new | Boolean | true → only products flagged "new". |
featured | Boolean | true → only products flagged "featured". |
<attribute_code> | String | Any filterable attribute code (e.g. color, size, brand). Value is the option id; comma-separate for multiple ("3,4"). |
Write the filter as a normal JSON object first:
{
"type": "simple",
"price_from": 10,
"price_to": 200
}Then pass it to filter as a single-line string with its quotes escaped:
query getFilteredProducts {
products(
filter: "{\"type\": \"simple\", \"price_from\": 10, \"price_to\": 200}"
first: 10
) {
totalCount
edges {
node {
_id
sku
name
price
formattedPrice
}
}
}
}Escaping is avoided entirely by writing the filter as a GraphQL block string, which accepts embedded quotes as-is:
query getFilteredProducts {
products(
filter: """{"type": "simple", "price_from": 10, "price_to": 200}"""
first: 10
) {
totalCount
edges {
node {
_id
sku
name
price
formattedPrice
}
}
}
}A variable keeps the query document static, which is what most clients want:
query getFilteredProducts($filter: String) {
products(filter: $filter, first: 10) {
totalCount
edges {
node {
_id
sku
name
price
formattedPrice
}
}
}
}{
"filter": "{\"type\": \"simple\", \"price_from\": 10, \"price_to\": 200}"
}Filter behaviour
Four rules decide what a filter actually returns:
| Rule | What it means |
|---|---|
| Keys combine | Every key you add narrows the result. Filters intersect — they never widen the set. |
| The value is always a string | filter is a String scalar, not an input object, so a real JSON object is rejected before the query runs. Build the string in the client — JSON.stringify({ type: 'simple', price_from: 10 }) — and the escaping is handled for you. |
| A price range needs two keys | Use price_from and price_to. The compound price=min,max form is REST-only and has no effect here. |
Price matches the product's own price | That attribute is 0 on configurable and bundle parents, whose real price lives in their variants or selections. Any price_from above 0 therefore drops both types — filter them by type and read minimumPrice / maximumPrice instead. |
Sorting
Use sortKey to pick the column and reverse to flip the direction. The common combinations:
| Sort | sortKey | reverse |
|---|---|---|
| A → Z | "TITLE" | false |
| Z → A | "TITLE" | true |
| Newest first | "CREATED_AT" | true |
| Oldest first | "CREATED_AT" | false |
| Cheapest first | "PRICE" | false |
| Most expensive first | "PRICE" | true |
Omitting sortKey orders by product ID, so a listing that cares about order should always set one.
How price sorting works
Sorting by PRICE does not order on the price field. It orders on minimumPrice — the effective price the shopper sees, which accounts for:
- Special price — a discounted price replaces the regular one.
- Configurable variants — the parent takes the lowest price across its variants.
- Neither applies —
minimumPriceequalsprice.
Display minimumPrice alongside price-sorted results, otherwise the order and the numbers on screen disagree.
Possible Returns
| Field | Type | Description |
|---|---|---|
edges | [ProductEdge!]! | Array of edges containing products and cursors. Each edge represents a connection between nodes. |
edges.node | Product! | The actual product object containing id, name, sku, price, and other product fields. |
edges.cursor | String! | Pagination cursor for this product. Use with after or before arguments. |
nodes | [Product!]! | Flattened array of products without edge information. |
pageInfo | PageInfo! | Pagination metadata object. |
pageInfo.hasNextPage | Boolean! | Whether there are more products after the current page. |
pageInfo.hasPreviousPage | Boolean! | Whether there are products before the current page. |
pageInfo.startCursor | String | Cursor of the first product on the current page. |
pageInfo.endCursor | String | Cursor of the last product on the current page. |
totalCount | Int! | Total number of products matching the query criteria. |
Price Fields
| Field | Type | Description |
|---|---|---|
price | Float | Base catalog price. Returns the converted numeric value based on the active currency set via X-Currency header. |
formattedPrice | String | Same as price but returned as a string with the currency symbol prefixed (e.g. "€84.99"). |
specialPrice | Float | Discounted price if a special price is set, otherwise null. Reflects currency conversion. |
formattedSpecialPrice | String | Same as specialPrice but with the currency symbol prefixed. |
minimumPrice | Float | The lowest effective price — accounts for special price and configurable variant pricing. Used for price sorting. Reflects currency conversion. |
formattedMinimumPrice | String | Same as minimumPrice but with the currency symbol prefixed. |
maximumPrice | Float | The highest effective price across all variants or configurations. Reflects currency conversion. |
formattedMaximumPrice | String | Same as maximumPrice but with the currency symbol prefixed. |
regularMinimumPrice | Float | The regular (non-discounted) minimum price before any special price is applied. Reflects currency conversion. |
formattedRegularMinimumPrice | String | Same as regularMinimumPrice but with the currency symbol prefixed. |
regularMaximumPrice | Float | The regular (non-discounted) maximum price before any special price is applied. Reflects currency conversion. |
formattedRegularMaximumPrice | String | Same as regularMaximumPrice but with the currency symbol prefixed. |
Product Types
Use the filter argument with "type" to fetch products of a specific kind. The filter value must be a single-line JSON string with escaped quotes.
| Type | Filter Value | Key Fields |
|---|---|---|
| Simple | "{\"type\": \"simple\"}" | price, specialPrice, images, attributeValues |
| Configurable | "{\"type\": \"configurable\"}" | variants, combinations, superAttributeOptions |
| Booking | "{\"type\": \"booking\"}" | bookingProducts (type, qty, location, availability) |
| Virtual | "{\"type\": \"virtual\"}" | price, specialPrice, attributeValues |
| Grouped | "{\"type\": \"grouped\"}" | groupedProducts → associatedProduct |
| Downloadable | "{\"type\": \"downloadable\"}" | downloadableLinks, downloadableSamples |
| Bundle | "{\"type\": \"bundle\"}" | bundleOptions → bundleOptionProducts → product |

