Skip to content

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_order map 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

PropertyValue
Field IDname
LabelGroup Name
TypeText
RequiredYes
Validationmax: 50 characters

Description: Name of the variant group.

Examples:

  • "Size"
  • "Crust Type"
  • "Spice Level"
  • "Protein"

Display Name

PropertyValue
Field IDdisplay_name
LabelDisplay Name
TypeText
RequiredNo

Description: Customer-facing name (if different from internal name).


Selection Type

PropertyValue
Field IDselection_type
LabelSelection Type
TypeSelect
Optionssingle, multiple
Defaultsingle

Description: How customers select variants.

Options:

  • Single: Choose one option (radio buttons)
  • Multiple: Choose multiple options (checkboxes)

Required

PropertyValue
Field IDrequired
LabelRequired
TypeToggle
Defaulttrue

Description: Whether customer must select an option.


Sort Order

PropertyValue
Field IDsort_order
LabelDisplay Order
TypeNumber
Default0

Description: Order in which groups appear.


Variant Option Fields

Option Name

PropertyValue
Field IDname
LabelOption Name
TypeText
RequiredYes
Validationmax: 50 characters

Description: Name of the variant option.

Examples:

  • "Small", "Medium", "Large"
  • "Thin Crust", "Thick Crust"
  • "Mild", "Medium", "Hot"

Price Adjustment

PropertyValue
Field IDprice_adjustment
LabelPrice Adjustment
TypeCurrency
Default0

Description: Price difference from base price.

Examples:

  • Small: €0 (base)
  • Medium: +€2.00
  • Large: +€4.00

SKU

PropertyValue
Field IDsku
LabelSKU
TypeText
RequiredNo

Description: Stock Keeping Unit for this variant.

Examples:

  • "PIZZA-MARG-S"
  • "PIZZA-MARG-M"
  • "PIZZA-MARG-L"

Track Inventory

PropertyValue
Field IDtrack_inventory
LabelTrack Inventory
TypeToggle
Defaultfalse

Description: Track stock for this specific variant.


Stock

PropertyValue
Field IDstock
LabelStock
TypeNumber
Depends Ontrack_inventory = true

Description: Current stock level for this variant.


Default Option

PropertyValue
Field IDis_default
LabelDefault Selection
TypeToggle
Defaultfalse

Description: Pre-selected option when item viewed.


Available

PropertyValue
Field IDavailable
LabelAvailable
TypeToggle
Defaulttrue

Description: Whether this option is currently available.


Variants vs Modifiers

FeatureVariantsModifiers
PurposeProduct variationsCustomizations
InventorySeparate per variantShared
SKUUnique per variantShared
PricingBase price changesAdd-on pricing
ExamplePizza sizesExtra toppings
SelectionUsually requiredOften 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.00

Variant 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: €16

Inventory 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 unaffected

Customer Impact

Online Ordering

  1. View item with variants
  2. Select required variant options
  3. See price update in real-time
  4. Add to cart with selections
  5. 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

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_id is 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_id cleared. 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_order map 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:

  1. Variant group not assigned to item
  2. Option marked unavailable
  3. Out of stock

Solutions:

  1. Assign variant group to item
  2. Enable option availability
  3. Restock variant

Problem: Price not updating

Causes:

  1. Price adjustment not set
  2. Cache issue
  3. Display bug

Solutions:

  1. Set price adjustment
  2. Clear cache
  3. Refresh page

Problem: Wrong variant in order

Causes:

  1. Customer selection error
  2. Default option wrong
  3. UI confusion

Solutions:

  1. Verify customer selection
  2. Check default settings
  3. 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 }
        ]
      }
    ]
  }
}