Products
The Products menu is the catalog's product-management surface — list, search, create, edit, copy, and delete products, plus manage each product's images, per-source inventory, and customer-group prices. It mirrors the admin Catalog → Products screen.
Product types
Every product has a type, fixed at creation. There are seven:
| Type | Notes |
|---|---|
simple | A standalone product with its own price and stock. |
virtual | Like simple but non-shippable (no weight/dimensions) — services, memberships. |
downloadable | Sells downloadable links (paid files) and samples (free previews); non-stockable. |
grouped | A storefront grouping of other simple products (linked products); has no own price. |
bundle | A configurable kit built from bundle options; its price is calculated from the chosen items. |
configurable | A parent with variants generated from variant-defining attributes (super_attributes, e.g. colour × size). Each variant is its own SKU with its own price/stock. |
booking | A bookable product (default / appointment / event / rental / table sub-types) with time slots; non-stockable. |
Composite types own no price or stock of their own. For configurable, bundle, grouped, and booking, the price and inventory live on the children — the variants, bundle items, linked products, or slots. Their top-level price / quantity are derived or empty.
Creating a product is two steps
Creation is deliberately minimal: a POST creates the shell from just sku + attribute_family_id + type (plus super_attributes for configurable). Everything else — name, description, price, images, categories, channels, inventory — is filled in afterwards via the update endpoint. This mirrors the admin's create-then-edit wizard.
status vs visibleIndividually
Two independent flags control storefront presence:
status—1enabled,0disabled. A disabled product is fully hidden from the storefront.visibleIndividually— whether the product appears in category and search listings. Variants and grouped components are usuallyfalse, reachable only through their parent, while still being enabled.
Both must be on for a product to be browsable on its own. Over REST status is a number and visibleIndividually a boolean; the GraphQL API returns both as strings, so the two transports are not interchangeable here.
Per-product sub-resources
The product edit screen's tabs map to their own endpoint groups, each scoped to one product:
- Images — upload, reorder, and delete a product's images.
- Inventories — read and bulk-update the per-inventory-source stock quantities.
- Customer-group prices — tiered prices that apply to specific customer groups.
These are not returned in full on the listing (they're detail-only); the single-product endpoint embeds them inline.
The product listing
GET /api/admin/catalog/products is the datagrid — a paginated { data, meta } envelope.
Listing filters (AND-combined)
All filters are combined with AND — every filter you add narrows the result set further. Pass them as query parameters:
| Filter | Type | Description |
|---|---|---|
channel | string | Channel code used to resolve per-channel values. |
name | string | Partial product-name match. |
sku | string | Partial SKU match. |
attribute_family | integer | Attribute-family ID. |
price_from / price_to | number | Price band (inclusive). price=50,200 is shorthand for price_from=50&price_to=200. |
product_id | string | A single ID, or a comma-separated list (e.g. 1,22,2705). |
status | integer | 0 (disabled) or 1 (active). |
type | string | One of the seven product types. |
locale | string | Locale code used to resolve translated values. |
Plus pagination and sort:
| Parameter | Type | Description |
|---|---|---|
page | integer | 1-based page number (default 1). |
per_page | integer | Page size (default 10, max 50). |
sort | string | product_id (default), sku, name, type, status, price, quantity, attribute_family, channel. |
order | string | asc or desc (default desc). |
Listing columns
Every row carries these scalar columns. Heavy fields are null on the listing (fetch them from the detail endpoint):
| Field | Type | Notes |
|---|---|---|
id | integer | Product ID. |
sku | string | SKU. |
name | string|null | Resolved for the active locale/channel. null for draft products with no name yet. |
type | string | Product type. |
status | integer | 1 active / 0 disabled. |
price | string|null | Base price (composite types carry no own price → null). |
formattedPrice | string|null | Locale-formatted base price. |
specialPrice | string|null | Discounted price, when a special price is set. |
formattedSpecialPrice | string|null | Locale-formatted special price. |
specialPriceFrom | string|null | Start date of the special-price window (null = always on). |
specialPriceTo | string|null | End date of the special-price window (null = no end). |
quantity | integer | Total stock across inventory sources. |
baseImageUrl | string|null | URL of the product's first image by position — the picture the admin listing shows. |
imagesCount | integer | Number of images. |
categoryId | integer|null | Lowest category id the product is in. |
categoryName | string|null | Every category the product is in, comma-separated. |
channel | string | Resolved channel code. |
locale | string | Resolved locale code. |
attributeFamilyId | integer | Attribute-family ID. |
attributeFamilyName | string | Attribute-family name. |
urlKey | string|null | Storefront URL slug. |
visibleIndividually | boolean | Appears in category/search listings. |
shortDescription / description | string|null | Resolved content. |
metaTitle / metaDescription / metaKeywords | string|null | SEO fields. |
weight | number|null | Product weight. |
featured | boolean | Featured flag. |
new | boolean | "New" flag. |
createdAt / updatedAt | string | Timestamps. |
taxCategoryId | integer|null | Detail-only — null on the listing. |
manageStock | boolean|null | Whether stock is tracked for this product. |
inStock | boolean|null | Detail-only — null on the listing. |
translations, images, categories, inventories, customerGroupPrices, superAttributes, variants, bundleOptions, linkedProducts, downloadableLinks, downloadableSamples | array|null | Relation blocks — all null on the listing; populated only on the detail endpoint. |
Actions
| Action | What it does |
|---|---|
| Copy | Duplicates an existing product into a new draft product (a fresh SKU is generated). |
| Mass delete | Deletes several products at once — body { "indices": [1, 22] }. Missing IDs are skipped. |
| Mass update status | Bulk enable/disable — body { "indices": [1, 22], "value": 0 } (0 = disable, 1 = active). |
| Export (CSV) | Downloads the listing as a CSV file, honouring the current filters; exports every matching row, not just the page. REST only. |
Endpoints in this menu
| Action | Endpoint |
|---|---|
| List products | GET /api/admin/catalog/products |
| Product detail | GET /api/admin/catalog/products/{id} |
| Create product | POST /api/admin/catalog/products |
| Update product | PUT /api/admin/catalog/products/{id} |
| Delete product | DELETE /api/admin/catalog/products/{id} |
| Copy product | POST /api/admin/catalog/products/{id}/copy |
| Mass delete | POST /api/admin/catalog/products/mass-delete |
| Mass update status | POST /api/admin/catalog/products/mass-update-status |
| Export products (CSV) | GET /api/admin/catalog/products/export |
| Upload images | POST /api/admin/catalog/products/{id}/images |
| Reorder images | PUT /api/admin/catalog/products/{id}/images/reorder |
| Delete image | DELETE /api/admin/catalog/products/{id}/images/{imageId} |
| List inventories | GET /api/admin/catalog/products/{id}/inventories |
| Update inventories | PUT /api/admin/catalog/products/{id}/inventories |
| List customer-group prices | GET /api/admin/catalog/products/{id}/customer-group-prices |
| Add customer-group price | POST /api/admin/catalog/products/{id}/customer-group-prices |
| Update customer-group price | PUT /api/admin/catalog/products/{id}/customer-group-prices/{priceId} |
| Delete customer-group price | DELETE /api/admin/catalog/products/{id}/customer-group-prices/{priceId} |
The canonical product listing is List products (GET /api/admin/catalog/products) above. There is also a separate slim Add-Product Search (GET /api/admin/products) used only by the Create-Order "Add Product" modal — not the product listing.
Reads require catalog.products.view; writes require the matching catalog.products.create / .edit / .delete permission.

