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#
GET /v1/modifier-groups
POST /v1/modifier-groups
PATCH /v1/modifier-groups/:ref
DELETE /v1/modifier-groups/:refA 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 (supportsisRequired)add_ingredients— add-on toppings (multi-select)remove_ingredients— removable ingredients
POST /v1/modifier-groups{
"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#
DELETE /v1/modifier-groups/:ref
DELETE /v1/modifier-groups/:ref?force=trueDeleting 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:
{
"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
modifierGroupsform (full group definitions on the product) is still accepted for backward-compatibility (create-or-reuse), butmodifierGroupExternalIdtakes precedence when both are present and is preferred.
Translations#
PUT /v1/modifier-groups/:ref/translations
GET /v1/modifier-groups/:ref/translationsTranslate 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.
{ "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:refroute above — one group, one translation, shared across products.