Menus
Menu recipes
Worked menu fragments for the scenarios real restaurants need — multi-select modifiers, nested choices, dayparted specials, and happy-hour discounts.
These are copy-and-adapt fragments for common menu scenarios. Each shows only the relevant part of the menu document — drop it into the full structure from Publish a menu. For the model behind them, see The menu model.
All prices are integer minor units (450 = $4.50), and every object carries an externalId you own.
Multi-select: choose up to 3, max 2 different
“Pick your toppings — up to 3 total, but no more than 2 different kinds.” The total count and the distinct count are separate rules:
{ "externalId": "grp-toppings", "name": "Toppings", "minSelections": 0, "maxSelections": 3, "maxUniqueSelections": 2, "options": [ { "externalId": "top-mushroom", "name": "Mushroom", "maxQuantity": 3, "price": { "amount": { "amountMinor": 150, "currency": "USD" } } }, { "externalId": "top-pepperoni", "name": "Pepperoni", "maxQuantity": 3, "price": { "amount": { "amountMinor": 200, "currency": "USD" } } }, { "externalId": "top-olives", "name": "Olives", "maxQuantity": 3, "price": { "amount": { "amountMinor": 100, "currency": "USD" } } } ]}maxSelections: 3caps the total quantity selected.maxUniqueSelections: 2caps the number of distinct options. A guest can take 2× Mushroom + 1× Olives (3 total, 2 kinds), but not three different toppings.maxQuantity: 3on each option allows doubling or tripling a single topping.
Nested modifiers
A choice that opens further choices — pick a side, and if it’s the salad, pick a dressing. Options can carry their own modifier groups:
{ "externalId": "itm-combo", "name": "Burger Combo", "variations": [{ "externalId": "var-combo", "name": "Regular", "price": { "amount": { "amountMinor": 1200, "currency": "USD" } } }], "modifierGroups": [ { "externalId": "grp-side", "name": "Choose your side", "minSelections": 1, "maxSelections": 1, "options": [ { "externalId": "side-fries", "name": "Fries" }, { "externalId": "side-salad", "name": "Side salad", "modifierGroups": [ { "externalId": "grp-dressing", "name": "Dressing", "minSelections": 1, "maxSelections": 1, "options": [ { "externalId": "drs-ranch", "name": "Ranch" }, { "externalId": "drs-vinaigrette", "name": "Vinaigrette" } ] } ] } ] } ]}Choosing Side salad reveals a required Dressing choice; choosing Fries doesn’t. Nest as deep as the menu needs.
Availability: daily specials
A breakfast section that only shows on weekday mornings. Put the window on the category (it narrows everything inside) or on a single item:
{ "externalId": "cat-breakfast", "name": "Breakfast", "availability": { "windows": [ { "dayOfWeek": 1, "startTime": "07:00", "endTime": "11:00" }, { "dayOfWeek": 2, "startTime": "07:00", "endTime": "11:00" }, { "dayOfWeek": 3, "startTime": "07:00", "endTime": "11:00" }, { "dayOfWeek": 4, "startTime": "07:00", "endTime": "11:00" }, { "dayOfWeek": 5, "startTime": "07:00", "endTime": "11:00" } ] }, "items": [{ "externalId": "itm-pancakes", "name": "Pancakes", "variations": [{ "externalId": "var-pancakes", "name": "Regular", "price": { "amount": { "amountMinor": 950, "currency": "USD" } } }] }]}dayOfWeekis0(Sunday) through6(Saturday); add one window per active day.startTime/endTimeare localHH:MM(24-hour), in the menu’s timezone — set the top-leveltimezone(it defaults to UTC).- An object with no
availabilityis always available.
This is how time-of-day menus work — there’s one menu per location, and availability windows carve it up.
Happy hour: a recurring discount
A recurring discount takes three top-level objects working together — what’s targeted (a product set), the discount math, and when it’s active (the promotion):
{ "productSets": [ { "externalId": "set-drinks", "name": "All drinks", "itemIdsAny": ["itm-latte", "itm-cold-brew"] } ], "discounts": [ { "externalId": "disc-happy-hour", "name": "20% off drinks", "benefitType": "percentage", "percentage": 20, "benefitTargetProductSetId": "set-drinks" } ], "promotions": [ { "externalId": "promo-happy-hour", "name": "Happy hour", "discountId": "disc-happy-hour", "applicationMode": "automatic", "availability": { "windows": [ { "dayOfWeek": 1, "startTime": "16:00", "endTime": "18:00" }, { "dayOfWeek": 2, "startTime": "16:00", "endTime": "18:00" }, { "dayOfWeek": 3, "startTime": "16:00", "endTime": "18:00" }, { "dayOfWeek": 4, "startTime": "16:00", "endTime": "18:00" }, { "dayOfWeek": 5, "startTime": "16:00", "endTime": "18:00" } ] } } ]}- The product set says what the discount applies to (here, two drink items; use
"allProducts": truefor the whole menu). - The discount says how much (20% off). Other
benefitTypes includeamount,set_price,free_item, andbxgy. - The promotion ties them together and says when:
applicationMode: "automatic"applies it without a code, andavailabilitygives the recurring weekly windows. UsevalidFrom/validUntilfor a one-off campaign instead.
86 an item
Mark something out of stock without restructuring the menu — set stockStatus on the item (or variation):
{ "externalId": "itm-latte", "name": "Latte", "stockStatus": "out_of_stock", "variations": [{ "externalId": "var-latte", "name": "Regular", "price": { "amount": { "amountMinor": 450, "currency": "USD" } } }] }stockStatus is in_stock, out_of_stock, or low_stock. Omitting it on a re-publish preserves the current value, so a stock update can be a small, targeted publish.
Combine them
These compose. A breakfast category (availability) can hold a build-your-own item (nested modifiers) with a “first topping free” group (freeSelectionCount), all while a happy-hour promotion runs on drinks. Publish the whole menu and Maple applies it as one diff — see Publish a menu.