Skip to main content

Variants & Axes

Retail variation is axis-driven. An axis like "Size" or "Color" is defined once at the organization level and reused across the whole catalog. Each product declares which axes apply to it, and each variant is one unique combination of axis values — a single SKU with its own price and stock. A T-shirt uses Size × Color; a laptop uses Storage × RAM; the model is the same.

Axis-driven product variationOrganization-wide variant axes (Size, Color) are bound to a product. Each product variant combines one value per axis into a unique SKU with its own price (or inherited) and stock status of in_stock, low_stock, or out_of_stock.ORGANIZATION-WIDE AXESSizetext · S · M · L · XLreusableColorswatch · ● ● ●reusablebound to product (ProductAxisBinding)ProductClassic T-Shirt · uses Size × Colorone value per axis = one SKUVARIANTS (SKUs)S · BlackTS-S-BLK · in_stockM · BlackTS-M-BLK · low_stockL · BlackTS-L-BLK · out_of_stockeach SKU prices &stocks independently
SDK coverage

Variant axes and product variants are managed through the TypeScript SDK (client.productVariantAxes, client.productVariants). The Python product guide manages pricing and stock directly on the product (stock_quantity, price); the schema below is the platform model.

Variant axes

An axis is defined once and reused. Its type controls how its values render.

import { VariantAxisType } from 'wiil-core-js';

const sizeAxis = await client.productVariantAxes.create({
name: 'Size',
type: VariantAxisType.TEXT,
values: [
{ id: 'sm', label: 'Small', sortOrder: 0 },
{ id: 'md', label: 'Medium', sortOrder: 1 },
{ id: 'lg', label: 'Large', sortOrder: 2 },
],
isActive: true,
});

const colorAxis = await client.productVariantAxes.create({
name: 'Color',
type: VariantAxisType.SWATCH,
values: [
{ id: 'black', label: 'Black', swatchColor: '#000000', sortOrder: 0 },
{ id: 'white', label: 'White', swatchColor: '#FFFFFF', sortOrder: 1 },
],
isActive: true,
});

const axis = await client.productVariantAxes.get('axis_123');
const byName = await client.productVariantAxes.getByName('Size');
const all = await client.productVariantAxes.list();
await client.productVariantAxes.update('axis_123', { id: 'axis_123', values: [/* ... */] });
await client.productVariantAxes.delete('axis_123');

VariantAxis fields

FieldTypeRequiredDescription
idstringautoUnique identifier
namestringYesAxis name ("Size", "Color", "Storage")
typeenumYestext, swatch, image, or numeric
valuesarrayYesAxis values (min 1)
isActivebooleanNoWhether the axis is active (default true)

VariantAxisValue

FieldTypeRequiredDescription
idstringYesUnique value ID
labelstringYesDisplay label ("Small", "Red", "256GB")
swatchColorstring | nullNoHex color for swatch type
imageIdstring | nullNoImage for image type
numericValuenumber | nullNoNumeric value for numeric type
sortOrderintegerNoDisplay order (default 0)

Product variants

A variant is a SKU: a specific combination of axis values, with fields that override the parent product (null = inherit).

const variant = await client.productVariants.create({
productId: 'product_123',
axisValues: { Size: 'Extra Large', Color: 'Blue' },
sku: 'TS-XL-BL',
price: 29.99,
isDefault: false,
isActive: true,
});

const loaded = await client.productVariants.get('variant_123');
const bySku = await client.productVariants.getBySku('TS-XL-BL');
const defaultVariant = await client.productVariants.getDefault('product_123');

const updated = await client.productVariants.update('variant_123', { id: 'variant_123', price: 32.99, isActive: true });

const batch = await client.productVariants.createBatch([
{ productId: 'product_123', axisValues: { Size: 'Medium', Color: 'Red' }, sku: 'TS-MD-RD', price: 24.99, isDefault: false, isActive: true },
{ productId: 'product_123', axisValues: { Size: 'Large', Color: 'Green' }, sku: 'TS-LG-GN', price: 26.99, isDefault: false, isActive: true },
]);

await client.productVariants.delete('variant_123');

ProductVariant fields

FieldTypeRequiredDescription
idstringautoUnique identifier
productIdstringYesParent product
axisValuesobjectYesMap of axis ID → selected value ID
skustring | nullNoSKU (overrides parent)
barcode / partNumber / globalTradeItemNumberstring | nullNoIdentifiers
pricenumber | nullNoVariant price (null = inherit from product)
cost / compareAtPricenumber | nullNoCost and sale-display price
stockQuantityinteger | nullNoPer-variant stock
lowStockThresholdinteger | nullNoPer-variant low-stock threshold
inventoryUnitenum | nullNoEACH, CASE, PACK, BOX, PALLET, …
weight / dimensionsNoShipping overrides
imageId / imageIdsNoVariant imagery
channelMappingsarray | nullNoPer-channel external variant IDs
isActivebooleanNoAvailable for sale (default true)
isDefaultbooleanNoDefault variant (default false)

Stock status

Each variant resolves a stockStatus from its quantity and threshold:

ValueMeaning
in_stockAvailable for purchase (or inventory not tracked)
low_stockAt or below the low-stock threshold
out_of_stockZero stock quantity

Product axis bindings

A ProductAxisBinding records that a product uses a given axis, with a displayOrder controlling the order axes appear on the product. Each binding sets productId, axisId, displayOrder, and isActive.

Query options

ResourceFiltersSort fields
VariantAxissearch, type, isActivename, createdAt
ProductVariantproductId, axisValueId, sku, isActive, inStocksku, price, stockQuantity, createdAt
tip

Define axes once at the organization level and bind them to products; track stock per variant (stockQuantity) so fulfillment is accurate; and use the override pattern only where a variant truly differs from its parent.