Skip to content

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:

TypeNotes
simpleA standalone product with its own price and stock.
virtualLike simple but non-shippable (no weight/dimensions) — services, memberships.
downloadableSells downloadable links (paid files) and samples (free previews); non-stockable.
groupedA storefront grouping of other simple products (linked products); has no own price.
bundleA configurable kit built from bundle options; its price is calculated from the chosen items.
configurableA 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.
bookingA 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 — enabled or disabled. A disabled product is fully hidden from the storefront.
  • visibleIndividually — whether the product appears in category and search listings. Variants and grouped components are usually off, reachable only through their parent, while still being enabled.

Both must be on for a product to be browsable on its own.

Over GraphQL both arrive as strings, "1" for on and the empty string "" for off — never true/false and never "0". Compare against "1".

Nested Data Is Field-Selectable

On the single-product query every nested block — translations, images, videos, categories, inventories, customerGroupPrices, channels, attributeValues, and the type-specific blocks such as variants, bundleOptions, and downloadableLinks — is a Relay connection. Sub-select it with edges { node { … } } and take only the fields you need:

graphql
images {
  edges {
    node {
      _id
      url
      position
    }
  }
}

Selecting a connection as a bare field is a schema error. Three fields go the other way and are plain JSON scalars queried bare: attributes, bookingProduct, and warnings — and all three resolve null over GraphQL, so read them from the REST detail instead.

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:

ArgTypeDescription
first / aftercursor paginationPage size (default 10, max 50) + cursor from a previous pageInfo.endCursor.
channelStringChannel code used to resolve per-channel values.
nameStringPartial product-name match.
skuStringPartial SKU match.
attribute_familyIntAttribute-family ID.
price_from / price_toFloatPrice band (inclusive).
product_idStringA single ID, or a comma-separated list (e.g. "1,22,2705").
statusInt0 (disabled) or 1 (active).
typeStringOne of the seven product types.
localeStringLocale code used to resolve translated values.
sortStringproduct_id (default), sku, name, type, status, price, quantity, attribute_family, channel.
orderStringasc 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):

FieldTypeNotes
idIDIRI (/api/admin/catalog/products/{id}).
_idIntNumeric product ID.
skuStringSKU.
nameStringResolved for the active locale/channel. null for draft products.
typeStringProduct type.
statusInt1 active / 0 disabled.
price / formattedPriceStringBase price (composite types carry no own price → null).
specialPrice / formattedSpecialPriceStringDiscounted price, when set.
specialPriceFrom / specialPriceToStringSpecial-price window (null = always on / no end).
quantityIntTotal stock across inventory sources.
baseImageUrlStringURL of the product's first image by position — the picture the admin listing shows.
imagesCountIntNumber of images.
categoryId / categoryNameInt / StringLowest category id the product is in, and every category it is in, comma-separated.
channel / localeStringResolved channel / locale.
attributeFamilyId / attributeFamilyNameInt / StringAttribute family.
urlKeyStringStorefront URL slug.
visibleIndividually / featured / newBooleanStorefront flags.
shortDescription / description / metaTitle / metaDescription / metaKeywordsStringResolved content / SEO.
weightFloatProduct weight.
createdAt / updatedAtStringTimestamps.
manageStockBooleanWhether stock is tracked for this product.
taxCategoryId / inStockDetail-onlynull on the listing.
translations, images, categories, inventories, customerGroupPrices, superAttributes, variants, bundleOptions, linkedProducts, downloadableLinks, downloadableSamplesRelation blocks — all null on the listing; populated only on the detail query.

Actions

ActionWhat it does
CopyDuplicates an existing product into a new draft product (a fresh SKU is generated).
Mass deleteDeletes several products at once — indices: [1, 22]. Missing IDs are skipped.
Mass update statusBulk 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

ActionOperation
List productsadminCatalogProducts query
Product detailadminCatalogProduct(id:) query
Create productcreateAdminCatalogProduct mutation
Update productupdateAdminCatalogProduct mutation
Delete productdeleteAdminCatalogProduct mutation
Copy productcreateAdminCatalogProductCopy mutation
Mass deletecreateAdminCatalogProductMassDelete mutation
Mass update statuscreateAdminCatalogProductMassUpdateStatus mutation
Upload imagesREST only (binary)
Reorder imagesreorderAdminCatalogProductImage mutation
Delete imagedeleteAdminCatalogProductImage mutation
List inventoriesadminCatalogProductInventories query
Update inventoriesupdateAdminCatalogProductInventory mutation
List customer-group pricesadminCatalogProductCustomerGroupPrices query
Add customer-group pricecreateAdminCatalogProductCustomerGroupPrice mutation
Update customer-group priceupdateAdminCatalogProductCustomerGroupPrice mutation
Delete customer-group pricedeleteAdminCatalogProductCustomerGroupPrice 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.

Reads require catalog.products.view; writes require the matching catalog.products.create / .edit / .delete permission.

Released under the MIT License.