Skip to content

Categories

The endpoint always filters to status = 1, so a category a merchant has disabled is never returned and its ID responds 404.

This is a flat list. For the nested menu structure use Get Category Tree.

Endpoints

MethodPathPurpose
GET/api/shop/categoriesFlat, paginated list of active categories
GET/api/shop/categories/{id}Single category by ID

Use the example switcher above the curl block to flip between the two.

Request Headers

HeaderRequiredDescription
AcceptYesapplication/json
X-STOREFRONT-KEYYesStorefront API key (pk_storefront_…)
X-LocaleNoOverride request locale
X-ChannelNoOverride channel scope

Query Parameters (collection only)

ParameterTypeDefaultDescription
pageinteger1Page number (1-based)
per_pageinteger15Items per page. Max 100 for this endpoint.
parent_idintegerReturn only direct children of this category ID. Accepts parentId as alias.

Pagination headers (X-Total-Count, X-Page, X-Per-Page, X-Total-Pages) are emitted on the collection. See Pagination.

Category Object Fields

Both endpoints return the same shape — the collection is an array of these objects, the single endpoint returns one.

FieldTypeDescription
idintegerCategory primary key.
positionintegerDisplay order.
statusboolean (0/1)Always 1 here — the storefront endpoints never return disabled categories.
displayModestringproducts_and_description, products, or description_only.
logoPathstringStorage path of the category image. Absent when no image is set, rather than null.
logoUrlstringFully-qualified image URL. Absent alongside logoPath.
urlstringStorefront URL of the category page.
minPrice / maxPricenumberCheapest and dearest product price in the category, for a price-range filter. Both are 0 when the category holds no priced products.
_lft / _rgtintegerNested-set tree pointers — internal bookkeeping, safe to ignore.
createdAt / updatedAtstringISO 8601 timestamps.
translationobjectTranslation for the request locale, inline — see below.
translationsarrayPath references to every stored translation, e.g. /api/shop/category_translations/1.
parentstringPath of the parent category. Absent on a root category.
childrenarrayPath references to the direct children, e.g. /api/shop/categories/5. Empty for a leaf.
filterableAttributesarrayAttributes flagged filterable for this category, inline. Empty when none are configured.

Two things differ from what a client might assume: children and translations are path references, not inline objects, so building a tree from this endpoint costs one request per level — use Get Category Tree instead. And null-valued fields are omitted entirely rather than returned as null, so a client must test for key presence, not for a null value.

Inline Translation Fields

FieldTypeDescription
idintegerTranslation primary key
categoryIdintegerOwning category ID
localestringLocale code (en, fr, de, …)
namestringLocalized category name
slugstringURL slug (e.g. electronics)
urlPathstringFull URL path including any parent slugs
descriptionstringHTML description shown on the category page
metaTitlestringSEO <title> value
metaDescriptionstringSEO meta description
metaKeywordsstringSEO meta keywords

See IRIs & HATEOAS for how to dereference the IRI fields.

Category Attribute Filters

Each category inlines the facets its product listing can be narrowed by, under filterableAttributes, plus minPrice and maxPrice for a price slider. There is no separate filters endpoint — reading the category is enough to build the whole facet sidebar.

Only attributes an admin flagged filterable appear here, so the array is already the correct facet set for that category. It is empty when none are configured.

Filter Fields

Each entry is a full attribute record. The ones a facet UI actually needs:

FieldDescription
codeThe query-parameter name to send to the product listing, e.g. color.
typeselect, multiselect, price, boolean, and so on — decides whether you render checkboxes, a slider, or a toggle.
adminNameBack-office label. Show translation.name to shoppers instead.
translationThe label for the request locale, as { name }. translations lists the rest as path references.
positionThe order the store configured; sort the sidebar by it.
optionsSelectable values, empty for price and other non-option types.
isConfigurableWhether variants are also built from this attribute — useful when a filter doubles as a variant selector.

Option Fields

FieldDescription
idThe value to send when filtering. Filters match option ids, never labels.
adminNameBack-office label for the option.
translationThe shopper-facing label for the request locale, as { label }.
sortOrderDisplay order within the facet.

Applying the Filters

Take the attribute's code as the parameter name and the option's id as the value, then send them to the product listing:

GET /api/shop/products?category_id=2&color=1&size=4,5&price=24.99,299.99
  • Comma-separate ids inside one attribute for an OR match — ?size=4,5 means size 4 or 5.
  • Separate parameters are AND-combined — the example asks for red products that are size 4 or 5.
  • price takes the minPrice,maxPrice pair from the category as its bounds; the comma separates the two numbers and is never a thousands mark.
  • Sending an option label rather than its id matches nothing and returns an empty list, not an error.

Full parameter reference is on Search Products.

Use Cases

  • Sidebar or footer category list — the flat collection is one call, and each row already carries url and the localised name inside translation.
  • Subcategory widget?parent_id=N returns the direct children as full objects, which the children path references alone would not give you.
  • Category-page facetsfilterableAttributes is inline, so a faceted filter needs no second request; minPrice and maxPrice bound a price slider.
  • Breadcrumbs — follow parent upwards; its absence marks the root.

Best Practices

  • Test for key presence, not for nulllogoPath, logoUrl, and parent are dropped from the payload when empty.
  • Do not build a tree from children — they are references; Get Category Tree returns the nested structure in one call.
  • Read names from translation, not the top level — the category object itself carries no name; it lives in the locale block.
  • Send X-Locale rather than dereferencing translations — the inline translation already follows the request locale.

Released under the MIT License.