Modifier groups

A modifier group is a reusable set of options a guest picks on a product — "Choose your milk", "Add toppings", "Remove ingredients". Groups are restaurant-scoped and reusable: define a group once, then attach it to as many products as you like. Editing the group (its options or prices) updates every product it is attached to. Available on every plan with an active subscription; the plan modifier-group limit applies.

Hybrid identity (:ref)#

Each group carries an immutable opaque id and an optional caller-defined externalId (set it on create). Wherever a path takes :ref you may pass either; externalId is matched first. Creating with an externalId that already exists → 409 EXTERNAL_ID_ALREADY_EXISTS. Unknown ref → 404 MODIFIER_GROUP_NOT_FOUND.

List uses the shared envelope { items, total, page, limit, totalPages } (page ≥1, limit 1–100).

CRUD#

http
GET    /v1/modifier-groups
POST   /v1/modifier-groups
PATCH  /v1/modifier-groups/:ref
DELETE /v1/modifier-groups/:ref

A group has a name, a type, an optional isRequired, and options. Each option references an ingredient by ingredientRef (its externalId or id) and an optional priceAdjustment (minor units, may be negative).

type is one of:

  • single_choice — pick exactly one (supports isRequired)
  • add_ingredients — add-on toppings (multi-select)
  • remove_ingredients — removable ingredients
http
POST /v1/modifier-groups
json
{
  "externalId": "alt-milk",
  "name": "Choose your milk",
  "type": "single_choice",
  "isRequired": true,
  "options": [
    { "ingredientRef": "oat-milk", "priceAdjustment": 2500 },
    { "ingredientRef": "soy-milk", "priceAdjustment": 2500 }
  ]
}

PATCH /v1/modifier-groups/:ref updates any subset of name, type, isRequired, options. When options is provided it replaces the group's option set; omit it to leave options untouched.

Each list item includes usedOnProducts — how many products the group is currently attached to.

Delete guard#

http
DELETE /v1/modifier-groups/:ref
DELETE /v1/modifier-groups/:ref?force=true

Deleting a group that is attached to one or more products → 409 MODIFIER_GROUP_IN_USE with usedOnProducts. Pass ?force=true to delete anyway (the product attachments are detached automatically).

Attaching groups to products (sync)#

Attach reusable groups to a product through the menu sync payload using modifierGroupExternalId — the full ordered list of group refs (externalId or id) attached to that product:

json
{
  "products": [
    {
      "externalId": "latte",
      "name": "Latte",
      "priceMinor": 6500,
      "modifierGroupExternalId": ["alt-milk", "extra-shots"]
    }
  ]
}

Sync upserts the joins in order and detaches any group not listed; modifierGroupExternalId: [] detaches all. This is the recommended agent path: create/edit groups once via /v1/modifier-groups, then attach by ref.

The legacy inline modifierGroups form (full group definitions on the product) is still accepted for backward-compatibility (create-or-reuse), but modifierGroupExternalId takes precedence when both are present and is preferred.

Translations#

http
PUT /v1/modifier-groups/:ref/translations
GET /v1/modifier-groups/:ref/translations

Translate the group name (the only translatable field) per locale, exactly like other entities. Because a group is shared, one translation serves every product it is attached to.

json
{ "translations": [{ "locale": "uk", "name": "Оберіть молоко" }] }

Deprecated: the composite route PUT/GET /v1/products/:ref/modifier-groups/:name/translations (addressing a group by product + exact name) still works for older integrations, but prefer the stable :ref route above — one group, one translation, shared across products.