Appearance
Variant Groups
Overview
Variant Groups allow you to create product variations like sizes, colors, or styles. Unlike modifiers, variants create separate SKUs with their own prices and inventory.
Key Purpose: Create product variations with different prices and inventory.
Purpose
This page lets you create and manage variant groups that bundle related items (e.g., different sizes of the same product) into a single selectable product with shared modifier groups.
Key Concepts
- Variant Group: A named grouping of existing items that represent variations of the same product, with a shared name, description, PLU, and optional modifier groups.
- Items as Variants: Unlike modifiers, each variant option is a full item with its own price, SKU, and inventory tracking; items are linked to the variant group via
variant_group_id. - Label Consistency: All items within a variant group must share the same label, and all attached modifier groups must also match that label; mismatches are rejected.
- Tax Rate Consistency: All items in a variant group must have the same tax rate code; mixed tax rates are rejected with a 409 error.
- Items Order: The variant group stores an
items_ordermap that controls the display sequence of variant options to the customer.
Actions
Create Variant Group
Add a new variant group by providing a name, description, PLU, and a list of existing items to bundle. Modifier groups can optionally be attached. Old standalone Square catalog objects are cleaned up for items being added to the group.
Edit Variant Group
Update the variant group's name, description, or item list. Items removed from the group have their variant_group_id cleared and are restored as standalone items. Newly added items are validated and linked.
Delete Variant Group
Remove a variant group permanently. All items that belonged to the group have their variant_group_id cleared so they revert to standalone items.
Location
- Backoffice Route:
/menus/variants - Backend Controller:
app/Http/Controllers/Api/VariantController.php - Vue Component:
src/views/menus/variants/index.vue
Concepts
Variant Group
A category of variations (e.g., "Size", "Style").
Variant Option
A specific variation within a group (e.g., "Small", "Medium", "Large").
Variant Combination
When multiple groups combine (e.g., Size + Crust = "Large Thin Crust").
Variant Group Fields
Group Name
| Property | Value |
|---|---|
| Field ID | name |
| Label | Group Name |
| Type | Text |
| Required | Yes |
| Validation | max: 50 characters |
Description: Name of the variant group.
Examples:
- "Size"
- "Crust Type"
- "Spice Level"
- "Protein"
Display Name
| Property | Value |
|---|---|
| Field ID | display_name |
| Label | Display Name |
| Type | Text |
| Required | No |
Description: Customer-facing name (if different from internal name).
Selection Type
| Property | Value |
|---|---|
| Field ID | selection_type |
| Label | Selection Type |
| Type | Select |
| Options | single, multiple |
| Default | single |
Description: How customers select variants.
Options:
- Single: Choose one option (radio buttons)
- Multiple: Choose multiple options (checkboxes)
Required
| Property | Value |
|---|---|
| Field ID | required |
| Label | Required |
| Type | Toggle |
| Default | true |
Description: Whether customer must select an option.
Sort Order
| Property | Value |
|---|---|
| Field ID | sort_order |
| Label | Display Order |
| Type | Number |
| Default | 0 |
Description: Order in which groups appear.
Variant Option Fields
Option Name
| Property | Value |
|---|---|
| Field ID | name |
| Label | Option Name |
| Type | Text |
| Required | Yes |
| Validation | max: 50 characters |
Description: Name of the variant option.
Examples:
- "Small", "Medium", "Large"
- "Thin Crust", "Thick Crust"
- "Mild", "Medium", "Hot"
Price Adjustment
| Property | Value |
|---|---|
| Field ID | price_adjustment |
| Label | Price Adjustment |
| Type | Currency |
| Default | 0 |
Description: Price difference from base price.
Examples:
- Small: €0 (base)
- Medium: +€2.00
- Large: +€4.00
SKU
| Property | Value |
|---|---|
| Field ID | sku |
| Label | SKU |
| Type | Text |
| Required | No |
Description: Stock Keeping Unit for this variant.
Examples:
- "PIZZA-MARG-S"
- "PIZZA-MARG-M"
- "PIZZA-MARG-L"
Track Inventory
| Property | Value |
|---|---|
| Field ID | track_inventory |
| Label | Track Inventory |
| Type | Toggle |
| Default | false |
Description: Track stock for this specific variant.
Stock
| Property | Value |
|---|---|
| Field ID | stock |
| Label | Stock |
| Type | Number |
| Depends On | track_inventory = true |
Description: Current stock level for this variant.
Default Option
| Property | Value |
|---|---|
| Field ID | is_default |
| Label | Default Selection |
| Type | Toggle |
| Default | false |
Description: Pre-selected option when item viewed.
Available
| Property | Value |
|---|---|
| Field ID | available |
| Label | Available |
| Type | Toggle |
| Default | true |
Description: Whether this option is currently available.
Variants vs Modifiers
| Feature | Variants | Modifiers |
|---|---|---|
| Purpose | Product variations | Customizations |
| Inventory | Separate per variant | Shared |
| SKU | Unique per variant | Shared |
| Pricing | Base price changes | Add-on pricing |
| Example | Pizza sizes | Extra toppings |
| Selection | Usually required | Often optional |
When to use Variants:
- Different sizes with different prices
- Different base products
- Need separate inventory tracking
- Different preparation
When to use Modifiers:
- Optional add-ons
- Customizations
- Shared inventory
- Same base product
Business Logic
Price Calculation
Base Item Price: €10.00
Size Variant: Large (+€4.00)
Crust Variant: Stuffed (+€2.00)
Final Price: €10.00 + €4.00 + €2.00 = €16.00Variant Combinations
Item: Pizza
├── Size Group
│ ├── Small (€10)
│ ├── Medium (€12)
│ └── Large (€14)
│
└── Crust Group
├── Regular (€0)
├── Thin (€0)
└── Stuffed (+€2)
Combinations:
- Small Regular: €10
- Small Thin: €10
- Small Stuffed: €12
- Medium Regular: €12
- Medium Thin: €12
- Medium Stuffed: €14
- Large Regular: €14
- Large Thin: €14
- Large Stuffed: €16Inventory per Variant
Item: T-Shirt
├── Size: Small - Stock: 25
├── Size: Medium - Stock: 50
├── Size: Large - Stock: 30
└── Size: XL - Stock: 15
Customer orders Medium:
- Medium stock: 50 → 49
- Other sizes unaffectedCustomer Impact
Online Ordering
- View item with variants
- Select required variant options
- See price update in real-time
- Add to cart with selections
- Cart shows variant details
Display
┌─────────────────────────────────────┐
│ Margherita Pizza │
│ From €10.00 │
├─────────────────────────────────────┤
│ Size * │
│ ○ Small (€10.00) │
│ ● Medium (€12.00) │
│ ○ Large (€14.00) │
├─────────────────────────────────────┤
│ Crust * │
│ ● Regular │
│ ○ Thin │
│ ○ Stuffed Crust (+€2.00) │
├─────────────────────────────────────┤
│ Total: €12.00 │
│ [Add to Cart] │
└─────────────────────────────────────┘Relations
Depends On
- Menu Items: Variants attached to items
Affects
- Online Ordering: Variant selection
- Kiosk: Variant selection
- Inventory: Per-variant stock
- Transactions: Variant recorded
- Reports: Sales by variant
Related Features
Business Rules
- All items in a variant group must have the same tax rate code; attempting to group items with different tax rates returns a 409 error ("All items must have same tax category").
- All items in a variant group must share the same label; items with mismatched labels are rejected with a 400 error.
- An item can only belong to one variant group at a time; adding an item that already belongs to another group is rejected with a 400 error.
- When items are removed from a variant group during an update, their
variant_group_idis cleared so they revert to standalone items and can be independently managed or added to a different group. - Creating a variant group triggers cleanup of old standalone Square catalog objects for all items being added, preventing ID conflicts when Square creates variation entries.
FAQs
- What is the difference between variants and modifiers? Variants represent distinct product versions (e.g., Small vs Large pizza) with their own price, SKU, and inventory. Modifiers are add-on customizations (e.g., extra cheese) that share the base item's inventory.
- Can I attach modifier groups to a variant group? Yes. Modifier groups can be attached at the variant group level so all items in the group share the same customization options. The modifier groups must have the same label as the items.
- What happens when I delete a variant group? The group is removed, but all items within it are preserved as standalone items with their
variant_group_idcleared. No items are deleted. - Can variant items have different prices? Yes. Each item in a variant group retains its own independent price. The variant group itself stores a display name and description, but pricing comes from each individual item.
- How is the display order of variant options determined? The variant group stores an
items_ordermap that controls the sequence. When creating or updating a group, the order is determined by the position of items in the submitted array.
Troubleshooting
Problem: Variant not showing
Causes:
- Variant group not assigned to item
- Option marked unavailable
- Out of stock
Solutions:
- Assign variant group to item
- Enable option availability
- Restock variant
Problem: Price not updating
Causes:
- Price adjustment not set
- Cache issue
- Display bug
Solutions:
- Set price adjustment
- Clear cache
- Refresh page
Problem: Wrong variant in order
Causes:
- Customer selection error
- Default option wrong
- UI confusion
Solutions:
- Verify customer selection
- Check default settings
- Improve variant display
Examples
Size Variants (Pizza)
json
{
"variant_group": {
"name": "Size",
"selection_type": "single",
"required": true,
"options": [
{
"name": "Small (10\")",
"price_adjustment": 0,
"sku": "PIZZA-S",
"is_default": false
},
{
"name": "Medium (12\")",
"price_adjustment": 3.00,
"sku": "PIZZA-M",
"is_default": true
},
{
"name": "Large (14\")",
"price_adjustment": 6.00,
"sku": "PIZZA-L",
"is_default": false
},
{
"name": "Family (16\")",
"price_adjustment": 10.00,
"sku": "PIZZA-F",
"is_default": false
}
]
}
}Crust Variants
json
{
"variant_group": {
"name": "Crust",
"selection_type": "single",
"required": true,
"options": [
{
"name": "Classic",
"price_adjustment": 0,
"is_default": true
},
{
"name": "Thin & Crispy",
"price_adjustment": 0,
"is_default": false
},
{
"name": "Stuffed Crust",
"price_adjustment": 2.50,
"is_default": false
},
{
"name": "Gluten-Free",
"price_adjustment": 3.00,
"is_default": false
}
]
}
}Drink Size Variants
json
{
"variant_group": {
"name": "Size",
"selection_type": "single",
"required": true,
"options": [
{
"name": "Regular (330ml)",
"price_adjustment": 0,
"is_default": true
},
{
"name": "Large (500ml)",
"price_adjustment": 1.00,
"is_default": false
}
]
}
}Protein Variants (Bowl)
json
{
"variant_group": {
"name": "Protein",
"selection_type": "single",
"required": true,
"options": [
{
"name": "Chicken",
"price_adjustment": 0,
"is_default": true
},
{
"name": "Beef",
"price_adjustment": 2.00,
"is_default": false
},
{
"name": "Salmon",
"price_adjustment": 4.00,
"is_default": false
},
{
"name": "Tofu (Vegan)",
"price_adjustment": 0,
"is_default": false
}
]
}
}Item with Multiple Variant Groups
json
{
"item": {
"name": "Build Your Bowl",
"base_price": 12.00,
"variant_groups": [
{
"name": "Base",
"required": true,
"options": [
{ "name": "White Rice", "price_adjustment": 0 },
{ "name": "Brown Rice", "price_adjustment": 0 },
{ "name": "Noodles", "price_adjustment": 1.00 },
{ "name": "Salad", "price_adjustment": 0 }
]
},
{
"name": "Protein",
"required": true,
"options": [
{ "name": "Chicken", "price_adjustment": 0 },
{ "name": "Beef", "price_adjustment": 2.00 },
{ "name": "Salmon", "price_adjustment": 4.00 },
{ "name": "Tofu", "price_adjustment": 0 }
]
},
{
"name": "Size",
"required": true,
"options": [
{ "name": "Regular", "price_adjustment": 0 },
{ "name": "Large", "price_adjustment": 3.00 }
]
}
]
}
}