Appearance
Variant Groups
Overview
A Variant Group bundles several existing menu items that represent variations of the same product (for example the small, medium and large versions of a pizza) into a single selectable product. The group itself carries a shared name, PLU and description, plus optional shared modifier groups; pricing, SKU/PLU and inventory stay on the individual items.
Key Purpose: Group existing items as selectable variations of one product so customers pick a variant (e.g. a size) instead of seeing separate items.
Availability: In production, variant groups are only exposed to merchants with a Square integration (
is_square_integrated). A non-Square production merchant sees no "New variant group" option on the Items list and no "Variant Group" type filter — outside Square, the feature is limited to the test and staging environments.
Purpose
This page lets you create and manage variant groups. You select existing items to act as the variants, give the group a name, PLU and description, order the variants, and optionally attach modifier groups that apply across all variants in the group.
Key Concepts
- Variant Group: A named grouping of existing items, with shared
name,plu,description, anitems_ordermap, and optional sharedmodifier_group_ids. - Items as Variants: Each variant is a full existing item with its own price, PLU and inventory. An item is linked to a group through its own
variant_group_idfield; the group has no separate "option" records of its own (except for Square-managed groups, which mirror Square variation IDs inoption_ids). - Label Consistency: All items in a group must share the same
label(the integration/source label such as Regular or Square), and any attached modifier groups must share that label too; mismatches are rejected. - Tax Category Consistency: All items in a group must have the same tax rate code; mixed tax categories are rejected.
- Items Order: The group stores an
items_ordermap ({ item_id: position }) that controls the display sequence of the variants. The order is taken from the position of items in the submitteditemsarray (drag-and-drop reordering in the UI).
Actions
Create Variant Group
Provide a name, PLU (auto-generated from the name if left blank) and optional description, then add a list of existing items to bundle as variants. Modifier groups can optionally be attached. When Square is integrated, the old standalone Square catalog objects for the items being added are cleaned up first to avoid ID conflicts when Square creates the variation entries.
Edit Variant Group
Update the group's name, PLU, description, item list or attached modifier groups. Items removed from the group have their variant_group_id cleared so they revert to standalone items; newly added items are validated and linked.
Delete Variant Group
Remove the variant group. Every item that belonged to the group has its variant_group_id cleared, so the items are preserved as standalone items — no items are deleted.
Location
- Backoffice create page:
/menus/items/variant-groups/new - Backoffice edit page:
/menus/items/variant-groups/edit/:id - Navigation: Reached from the Items list (
/menus/items) via the "Add" menu. Variant groups appear in the same list as items. In production the "New variant group" entry and the "Variant Group" type filter are shown only to Square-integrated merchants, and the entry is additionally hidden when the location's connected POS owns the catalog read-only (see Business Rules). - Backend API base:
/back-office/variant-groups - Backend Controller:
app/Http/Controllers/Api/BackOffice/VariantGroupController.php - Backend Model:
app/RawModels/VariantGroup.php(Mongomenuscollection,model: variant_group) - Vue Form:
src/components/forms/variant-group/VariantGroupForm.vue(sub-views undersrc/views/variant-group/) - Vuex module:
src/store/modules/variantGroup.ts
API Endpoints
All endpoints are under the back-office API prefix /back-office/variant-groups.
| Method | Path | Action | Permission |
|---|---|---|---|
| POST | /back-office/variant-groups | Create a variant group | create-variant-groups |
| GET | /back-office/variant-groups/{id} | Show a variant group | view-items |
| PUT | /back-office/variant-groups/{id} | Update a variant group | edit-variant-groups |
| DELETE | /back-office/variant-groups/{id} | Delete a variant group | delete-variant-groups |
Variant Group Fields
These are the fields accepted by the create/update request (StoreVariantGroupRequest).
Name
| Property | Value |
|---|---|
| Field ID | name |
| Label | Name |
| Type | Text |
| Required | Yes |
| Validation | required, string |
Description: Name of the variant group. Stored in details.default.name. Used to auto-generate the PLU when no PLU is supplied.
Examples:
- "Margherita Pizza"
- "House Latte"
PLU
| Property | Value |
|---|---|
| Field ID | plu |
| Label | PLU |
| Type | Text |
| Required | Yes |
| Validation | required, string, unique per location |
Description: Product Look-Up code for the group. If omitted, it is auto-generated from the name. Must be unique within the location. For Square-sourced groups the PLU is taken from the first item and is not editable.
Description
| Property | Value |
|---|---|
| Field ID | description |
| Label | Description |
| Type | Textarea |
| Required | No |
| Validation | nullable, string (UI limits to 300 characters) |
Description: Optional description of the variant group. Stored in details.default.description.
Category
| Property | Value |
|---|---|
| Field ID | category_id |
| Label | Category |
| Type | Select |
| Required | No |
| Validation | nullable; the category must exist for this merchant |
Description: Optional category for the variant group, mirroring the same field on an item. Leaving it empty is valid — a group does not have to be categorised.
Items (Variations)
| Property | Value |
|---|---|
| Field ID | items |
| Label | Variations |
| Type | Array of { id } |
| Required | Yes |
| Validation | required, array; each item must exist; each entry needs items.*.id |
Description: The existing items to bundle as variants. The submitted order becomes the items_order map. Each item's variant_group_id is set to this group. All items must share the same label and the same tax category.
Modifier Groups
| Property | Value |
|---|---|
| Field ID | modifier_groups |
| Label | Modifier Groups |
| Type | Array of { id } |
| Required | No |
| Validation | nullable, array; each modifier group must exist; each entry needs modifier_groups.*.id |
Description: Optional modifier groups attached at the group level so they apply across all variants. Attached modifier groups must share the items' label.
Status
| Property | Value |
|---|---|
| Field ID | status |
| Label | Status |
| Type | String (API only — not exposed on the back-office form) |
| Required | No |
| Validation | nullable, string, in: Active, Inactive |
Description: Active/Inactive status of the group. Defaults to Active when not set. The back-office create/edit form never sends status, so groups created there are always Active; there is no Activate/Deactivate control for variant groups in the UI.
Location
| Property | Value |
|---|---|
| Field ID | location_id |
| Label | Location |
| Type | String (location id) |
| Required | Yes (on create only) |
| Validation | required on create, string, must exist in locations |
Description: The location the variant group belongs to. Required when creating; not required (and not changed) on update.
Variants vs Modifiers
| Feature | Variant Group | Modifiers |
|---|---|---|
| What it is | A group of existing items | Add-on choices on an item |
| Pricing | Each variant item keeps its own price | Add-on price per modifier |
| PLU / inventory | Per variant item | On the base item |
| Example | Small / Medium / Large pizza | Extra cheese, no onions |
| Selection | Customer picks one variant | Customer picks add-ons |
When to use a Variant Group:
- Different versions of a product that already exist as separate items (sizes, flavours)
- Each version has its own price, PLU and inventory
- You want them to appear as one selectable product instead of separate items
When to use Modifiers:
- Optional add-ons or customisations on a single item
- Shared with the base item rather than separate products
Business Logic
Pricing
The variant group does not store a price. Pricing comes from the individual items in the group. The back office shows a price range across the group's items (price_range with min/max); when both ends match it shows a single price. Modifier prices on the items widen that range.
How variants attach to items
There is no per-option record holding a price adjustment or SKU. Attachment is one-directional:
- The item carries the link in its own
variant_group_idfield. - The group carries the ordering in its
items_ordermap and (for Square only) the Square variation IDs inoption_ids. - On save, each submitted item's
variant_group_idis set to the group id, and the group'sitems_orderis rebuilt from the submitted order.
Square integration
When Square is integrated, creating a group (or adding items to one) first deletes the old standalone Square catalog objects for those items and clears their Square IDs, so Square can recreate them as variations under the group. Deleting a group syncs the deletion to Square; for non-Square setups, deletion simply clears variant_group_id on the affected items.
Customer Impact
Online Ordering and Kiosk
A variant group is serialized to the storefront/kiosk as a single product whose variants are the grouped items, together with the group's shared modifier_groups, image (taken from the first matching item) and a base price range. Customers pick one of the variants.
Exact storefront/kiosk rendering of the variant selector is not verified here (the back-end only provides the serialized
variants,modifier_groupsandpricerange).
Relations
Depends On
- Menu Items: A variant group is built from existing items, each linked via
variant_group_id.
Affects
- Online Ordering: Items in a group are presented as variants of one product.
- Kiosk: Same grouping is used for the kiosk menu.
- Modifier Groups: Optional shared modifier groups can be attached to the group.
- Square / third-party integrations: Creates/updates/deletes are synced to active integrations.
Related Features
Business Rules
- Creating a variant group is blocked with HTTP 422 when the location's connected POS owns the catalog read-only — currently Hendrickx and Vanhoutte (both Kassanet/Vectron). The error reads "This action isn't available while {POS} is your connected POS — its catalog is managed in {POS}." The back office mirrors this by hiding the "New variant group" option for those merchants.
- All items in a variant group must have the same tax rate code; grouping items with different tax categories returns a 409 error ("All items must have same tax category.").
- All items in a variant group must share the same label; mismatched labels return a 400 error ("All items must have the same label").
- Any attached modifier groups must share the items' label; a mismatch returns a 400 error ("All modifier groups must have the same label").
- An item can only belong to one variant group at a time; adding an item that already belongs to another group returns a 400 error ("Item …: is already in another variant group").
- The PLU must be unique per location; if no PLU is provided it is auto-generated from the name.
- When items are removed from a group during an update, their
variant_group_idis cleared so they revert to standalone items. - Deleting a group preserves its items as standalone items (their
variant_group_idis cleared); no items are deleted. - For Square-sourced groups, the PLU, item list and modifier groups are uneditable (
uneditable_fields). - Creating a group (or adding items) cleans up the old standalone Square catalog objects for those items to prevent ID conflicts when Square creates variation entries.
FAQs
- What is the difference between a variant group and modifiers? A variant group bundles existing items (e.g. Small vs Large pizza), each keeping its own price, PLU and inventory. Modifiers are add-on customisations on a single item (e.g. extra cheese).
- Do I create new variants here, or pick existing items? You pick existing items. A variant group is built from items that already exist in your menu; it does not create new items with their own price-adjustment fields.
- Can I attach modifier groups to a variant group? Yes. Modifier groups attached at the group level apply across all variants. They must have the same label as the items.
- What happens when I delete a variant group? The group is removed, but every item in it is preserved as a standalone item with its
variant_group_idcleared. No items are deleted. - Can the variants have different prices? Yes. Each item keeps its own price; the group itself stores no price and shows a price range across its items.
- How is the display order of variants determined? The group stores an
items_ordermap built from the order of items in the submitted array; the back office lets you drag to reorder. - Why can't I edit the PLU or items on some groups? Groups sourced from Square have their PLU, items and modifier groups locked (
uneditable_fields) because Square owns that data.
Troubleshooting
Problem: "This action isn't available while {POS} is your connected POS — its catalog is managed in {POS}." (HTTP 422)
Cause: The location is connected to a POS whose catalog is read-only inbound (Hendrickx or Vanhoutte, both Kassanet/Vectron). A variant group created in Upvendo could not be pushed back to that POS.
Solution: Create the variation structure in the POS instead; it syncs into Upvendo. The "New variant group" option is hidden in the back office for these merchants, so this error normally only appears when calling the API directly.
Problem: I can't find "New variant group" on the Items list
Cause: In production the option is only shown to Square-integrated merchants, and it is hidden when the connected POS owns the catalog read-only.
Solution: Nothing to fix — the feature is not offered for that merchant in production.
Problem: "All items must have same tax category."
Cause: The selected items have different tax rate codes.
Solution: Only group items that share the same tax category.
Problem: "All items must have the same label" / "All modifier groups must have the same label"
Cause: The selected items (or attached modifier groups) come from different sources/labels.
Solution: Group only items that share the same label, and attach only modifier groups with the same label.
Problem: "Item …: is already in another variant group"
Cause: One of the selected items already belongs to a different variant group.
Solution: Remove the item from its other group first, or choose a different item.
Examples
Create request (POST /back-office/variant-groups)
json
{
"name": "Margherita Pizza",
"plu": "MAR250616120000",
"description": "Choose your size",
"location_id": "664b1f...",
"items": [
{ "id": "664c01..." },
{ "id": "664c02..." },
{ "id": "664c03..." }
],
"modifier_groups": [
{ "id": "665a10..." }
]
}Update request (PUT /back-office/variant-groups/{id})
json
{
"name": "Margherita Pizza",
"plu": "MAR250616120000",
"description": "Choose your size",
"items": [
{ "id": "664c02..." },
{ "id": "664c01..." }
],
"modifier_groups": []
}Reordering the
itemsarray changes the saveditems_order. Dropping664c03...from the list clears that item'svariant_group_id, restoring it as a standalone item.
Show response (GET /back-office/variant-groups/{id})
json
{
"id": "665d77...",
"location_id": "664b1f...",
"name": "Margherita Pizza",
"plu": "MAR250616120000",
"description": "Choose your size",
"items": [
{
"id": "664c01...",
"name": "Margherita Pizza (Small)",
"image_url": "https://...",
"modifier_groups": [],
"price_range": { "min": 10, "max": 10 }
}
],
"modifier_groups": [
{
"id": "665a10...",
"name": "Extras",
"modifiers": [ { "id": "665a11...", "name": "Extra cheese" } ]
}
],
"uneditable_fields": []
}