Skip to main content
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: 3 caps the total quantity selected.
  • maxUniqueSelections: 2 caps the number of distinct options. A guest can take 2× Mushroom + 1× Olives (3 total, 2 kinds), but not three different toppings.
  • maxQuantity: 3 on each option allows doubling or tripling a single topping.
Make the first selections free with freeSelectionCount. For example "freeSelectionCount": 1 charges only from the second topping onward.

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.
Order resources currently expose the directly-selected, first-level modifiers on each line item — deeper nested selections aren’t expanded into the order resource yet.

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" } } }] }]
}
  • dayOfWeek is 0 (Sunday) through 6 (Saturday); add one window per active day.
  • startTime / endTime are local HH:MM (24-hour), in the menu’s timezone — set the top-level timezone (it defaults to UTC).
  • An object with no availability is 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": true for the whole menu).
  • The discount says how much (20% off). Other benefitTypes include amount, set_price, free_item, and bxgy.
  • The promotion ties them together and says when: applicationMode: "automatic" applies it without a code, and availability gives the recurring weekly windows. Use validFrom / validUntil for a one-off campaign instead.
The IDs wiring these together — discountId, benefitTargetProductSetId, and the itemIdsAny members — are the externalIds you assigned. Cross-references in a menu always use your IDs, never Maple’s. The promotion’s availability windows use the menu’s timezone (set the top-level timezone; it defaults to UTC).

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.