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: createAdminCatalogProduct creates the shell from just sku + attributeFamilyId + type (plus superAttributes for configurable). Everything else — name, description, price, images, categories, channels, inventory — is filled in afterwards via updateAdminCatalogProduct. 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/search listings. Variant products and grouped-component products are usually set to0(reachable only through their parent), while still beingstatus = 1.
Both must effectively be on for a product to be browsable on its own.
Nested data is returned whole
On the single-product query, nested blocks (translations, images, categories, inventories, customerGroupPrices, and the type-specific blocks like variants / bundleOptions / downloadableLinks) are returned as whole JSON — query each as a bare field (translations, not translations { … }); the entire array comes back. They resolve over GraphQL on the detail query.
Per-product sub-resources
The product edit screen's tabs map to their own operations, each scoped to one product:
- Images — upload (REST only — binary), 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 query embeds them inline.
The product listing
adminCatalogProducts is the datagrid query — cursor pagination via first / after. Every scalar field (including the special-price columns) resolves over GraphQL.
Listing arguments (filters are AND-combined)
All filter arguments are combined with AND — every one you add narrows the result set:
| Arg | Type | Description |
|---|---|---|
first / after | cursor pagination | Page size (default 10, max 50) + cursor from a previous pageInfo.endCursor. |
channel | String | Channel code used to resolve per-channel values. |
name | String | Partial product-name match. |
sku | String | Partial SKU match. |
attribute_family | Int | Attribute-family ID. |
price_from / price_to | Float | Price band (inclusive). |
product_id | String | A single ID, or a comma-separated list (e.g. "1,22,2705"). |
status | Int | 0 (disabled) or 1 (active). |
type | String | One of the seven product types. |
locale | String | Locale code used to resolve translated values. |
sort | String | product_id (default), sku, name, type, status, price, quantity, attribute_family, channel. |
order | String | asc or desc (default desc). |
Listing node fields
Each node carries these scalar columns. Heavy fields are null on the listing (query them on the detail query):
| Field | Type | Notes |
|---|---|---|
id | ID | IRI (/api/admin/catalog/products/{id}). |
_id | Int | Numeric product ID. |
sku | String | SKU. |
name | String | Resolved for the active locale/channel. null for draft products. |
type | String | Product type. |
status | Int | 1 active / 0 disabled. |
price / formattedPrice | String | Base price (composite types carry no own price → null). |
specialPrice / formattedSpecialPrice | String | Discounted price, when set. |
specialPriceFrom / specialPriceTo | String | Special-price window (null = always on / no end). |
quantity | Int | Total stock across inventory sources. |
baseImageUrl | String | Medium-cache base image URL. |
imagesCount | Int | Number of images. |
categoryId / categoryName | Int / String | Primary category. |
channel / locale | String | Resolved channel / locale. |
attributeFamilyId / attributeFamilyName | Int / String | Attribute family. |
urlKey | String | Storefront URL slug. |
visibleIndividually / featured / new | Boolean | Storefront flags. |
shortDescription / description / metaTitle / metaDescription / metaKeywords | String | Resolved content / SEO. |
weight | Float | Product weight. |
createdAt / updatedAt | String | Timestamps. |
taxCategoryId / manageStock / inStock | — | Detail-only — null on the listing. |
translations, images, categories, inventories, customerGroupPrices, superAttributes, variants, bundleOptions, linkedProducts, downloadableLinks, downloadableSamples | — | Relation blocks — all null on the listing; populated only on the detail query. |
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 — indices: [1, 22]. Missing IDs are skipped. |
| Mass update status | Bulk enable/disable — indices: [1, 22], value: 0 (0 = disable, 1 = active). |
| Export (CSV) | The datagrid "Export" button is REST only (binary file streams aren't expressible over GraphQL) — see Export Products. |
Operations in this menu
| Action | Operation |
|---|---|
| List products | adminCatalogProducts query |
| Product detail | adminCatalogProduct(id:) query |
| Create product | createAdminCatalogProduct mutation |
| Update product | updateAdminCatalogProduct mutation |
| Delete product | deleteAdminCatalogProduct mutation |
| Copy product | createAdminCatalogProductCopy mutation |
| Mass delete | createAdminCatalogProductMassDelete mutation |
| Mass update status | createAdminCatalogProductMassUpdateStatus mutation |
| Upload images | REST only (binary) |
| Reorder images | reorderAdminCatalogProductImage mutation |
| Delete image | deleteAdminCatalogProductImage mutation |
| List inventories | adminCatalogProductInventories query |
| Update inventories | updateAdminCatalogProductInventory mutation |
| List customer-group prices | adminCatalogProductCustomerGroupPrices query |
| Add customer-group price | createAdminCatalogProductCustomerGroupPrice mutation |
| Update customer-group price | updateAdminCatalogProductCustomerGroupPrice mutation |
| Delete customer-group price | deleteAdminCatalogProductCustomerGroupPrice mutation |
The canonical product listing is List products (adminCatalogProducts query) above. There is also a separate slim Add-Product Search (adminProducts query) used only by the Create-Order "Add Product" modal — not the product listing.
All Products operations require an admin Bearer token — see Authentication. Reads require catalog.products.view; writes require the matching catalog.products.create / .edit / .delete permission.

