> ## Documentation Index
> Fetch the complete documentation index at: https://docs.maple.inc/llms.txt
> Use this file to discover all available pages before exploring further.

# Publish a menu

> Send a location’s full menu as one JSON document keyed by your own stable IDs, then read it back. Publishing is a diff, so re-publishing is safe.

A location's menu is the catalog customers order from. You publish it as **one JSON document** that describes the whole menu — categories, items, variations, modifiers, and the tax and fee definitions that apply. Maple keys everything off the **external IDs you assign**, so the `menu_entity_id` on every order line item maps straight back to your own catalog.

Publishing requires the `menus:write` scope and an **active connection** to the location. Reading requires `menus:read`. See [How Maple works](/developer-api/how-maple-works) for connections.

## The shape of a menu

A menu is a tree with shared definitions alongside it:

```
menu
├── categories[]          (required)
│   └── items[]
│       └── variations[]  (required: at least one — the price lives here)
│           └── modifierGroups[]   (inline, or a reference to a shared group)
├── modifierGroups[]      (shared definitions, referenced by groupId)
├── taxCategories[] / taxRates[]
├── fees[]
└── discounts[] / promotions[] / coupons[]
```

Two rules carry most of the model:

* **Every object has an `externalId`** — your stable handle for it. You choose these; Maple never changes them. Re-publishing with the same `externalId` updates the same object. The response also mints Maple IDs (`item_…`, `var_…`, `ctg_…`) for reference, but you address objects by your `externalId`.
* **The price lives on the variation.** Even a simple item has at least one variation (call it "Regular"). All prices are **integer minor units** — `450` is \$4.50.

## A minimal menu

The smallest valid document is a currency and one category containing one item with one variation:

```bash theme={null}
curl -X POST $MAPLE_BASE/locations/{locationId}/menu \
  -H "Authorization: Bearer $MAPLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "USD",
    "categories": [
      {
        "externalId": "cat-drinks",
        "name": "Drinks",
        "items": [
          {
            "externalId": "itm-latte",
            "name": "Latte",
            "variations": [
              { "externalId": "var-latte-reg", "name": "Regular", "price": { "amount": { "amountMinor": 450, "currency": "USD" } } }
            ]
          }
        ]
      }
    ]
  }'
```

A successful publish returns the resulting menu with every collection materialized and Maple IDs filled in. The `externalId`s you sent are echoed back unchanged.

## Add shared modifiers

Define a modifier group once at the top level and reference it by `groupId` wherever it applies — so "Milk options" stays in one place across every drink:

```json theme={null}
{
  "currency": "USD",
  "modifierGroups": [
    {
      "externalId": "grp-milk",
      "name": "Milk",
      "minSelections": 0,
      "maxSelections": 1,
      "options": [
        { "externalId": "mod-whole", "name": "Whole milk" },
        { "externalId": "mod-oat", "name": "Oat milk", "price": { "amount": { "amountMinor": 75, "currency": "USD" } } }
      ]
    }
  ],
  "categories": [
    {
      "externalId": "cat-drinks",
      "name": "Drinks",
      "items": [
        {
          "externalId": "itm-latte",
          "name": "Latte",
          "variations": [{ "externalId": "var-latte-reg", "name": "Regular", "price": { "amount": { "amountMinor": 450, "currency": "USD" } } }],
          "modifierGroups": [{ "groupId": "grp-milk" }]
        }
      ]
    }
  ]
}
```

A modifier group entry is either an **inline definition** (carries `name` and `options`) or a **reference** (`{ "groupId": "grp-milk" }`). Don't mix the two shapes in one entry — a payload that does is rejected rather than silently truncated. Items, variations, and individual options can each carry modifier groups, which lets you nest options where the menu calls for it.

<Tip>
  Tax categories and rates, fees (like a bottle deposit), discounts, promotions, and coupons are all optional
  top-level collections, each addressed by its own `externalId` and attached to items by ID (`taxCategoryId`,
  `feeIds`, and so on). The [endpoint reference](/developer-api/api-reference) documents every field.
</Tip>

## Publishing semantics

Every publish sends the **entire menu** as its desired state. Maple diffs it against the current menu (keyed by `externalId`) and applies the result as a new version. A few rules follow from that:

* **The document is the whole menu.** Whatever you send becomes the menu. An object you published before but leave out of a later publish is **removed** — to keep something, include it every time.
* **`stockStatus` is the exception.** It's a field-level value: leaving it off an object you *do* include preserves that object's current stock (new objects default to `in_stock`). So 86-ing an item is still a complete publish that includes every object — you just set one field.
* **Identity is your `externalId`.** The same `externalId` updates the same object across publishes; a new one creates a new object. Use IDs that are stable in your system (a SKU or primary key), not display names.
* **Order is preserved.** Categories, items, variations, and options appear in the order you send them.
* **Validation is all-or-nothing.** If anything is invalid you get a `400` listing every issue, and the menu is left exactly as it was. Fix them all and re-send.
* **Identical re-publishes are no-ops.** Sending the same document changes nothing and creates no churn, so you can safely publish on every menu change.
* **Each change is versioned.** A content change cuts a new version; the location always serves the current one.

Because the document is the full desired state, the simplest integration re-publishes the entire menu whenever your source changes. You never compute diffs — Maple does.

### Set the timezone for availability

Availability windows (dayparting and happy hours) are evaluated in the **menu's timezone**. Set it with the top-level `timezone` field — an IANA name like `America/New_York`:

```json theme={null}
{ "currency": "USD", "timezone": "America/New_York", "categories": [ /* … */ ] }
```

If you omit it, the menu's timezone defaults to **UTC**, which is almost never right for time-based rules. The timezone is established on your **first** publish, so set it correctly up front.

## Read it back

```bash theme={null}
curl $MAPLE_BASE/locations/{locationId}/menu \
  -H "Authorization: Bearer $MAPLE_KEY"
```

Returns the current published menu — categories, items, and variations with prices, shared modifier groups, and tax, fee, and discount definitions — every object echoing the `externalId` you published it under. A location with no published menu returns a `404`.

## Know when a publish lands

Subscribe to the menu sync events to be notified as a published menu propagates:

| Event                 | Meaning                             |
| --------------------- | ----------------------------------- |
| `menu.sync.completed` | A menu sync finished for a location |
| `menu.sync.failed`    | A menu sync failed for a location   |

See [Webhooks](/developer-api/concepts/webhooks) to subscribe and verify these.

## Next

<CardGroup cols={2}>
  <Card title="Receive and decide orders" icon="receipt" href="/developer-api/guides/receive-orders">
    Order line items reference your menu by the `externalId` you assign.
  </Card>

  <Card title="Full menu schema" icon="code" href="/developer-api/api-reference">
    Every field on every menu object, in the endpoint reference.
  </Card>
</CardGroup>
