Menus are named trees of navigation items. Reads require read_menus; writes require write_menus. Only documented request fields are accepted.
Start with GET /menus/{menuId}?include_items=false to obtain metadata, item_count and the current revision at any menu size. Full menu requests and responses are limited to 4 MiB of serialized JSON; larger documents return HTTP 413 MENU_PAGE_ITEMS_REQUIRED. Page root items and children through GET /menus/{menuId}/items, using parent_id, page and limit. Read live limits from /menus/limits; supported stored capacity and transport size are separate limits. Numeric resource IDs, including EU IDs starting at 281474976710656, are decimal strings in link_value.
Save with the revision you edited. Send exactly one of a complete items tree or ordered ops; keep item IDs to preserve translations. Use before_id or after_id to move relative to a sibling, including on later pages. Never send a visible page index as a global position or coalesce interleaved moves. child_count and subtree_count include unloaded descendants. Empty operations preserve items; an empty full tree deletes them. Revision conflicts retain local edits until you choose how to reconcile.
Canvas header/mobile navigation renders three levels; its footer renders only roots. Labels have no destination. Menu item images and promotion slots are not supported; /menus/limits declares placement capabilities. Resource links resolve current published slugs and hide unavailable leaves; surviving children keep a heading. Custom URLs do not follow destination renames or deletion.
Renaming reserves the old handle for the same menu. Up to 20 historical handles are retained; another menu cannot claim them. handle_aliases and backend-resolved handle_history expose reservations. Existing references never expire blindly.
CSV imports add or update items in existing menus. They do not create menu definitions or preserve empty menus and are not a complete backup. A staged preview expires after 24 hours or relevant source-version changes; diagnostics are paginated in a results dialog and operation feedback is backend-resolved. Menu import/export outcomes reach owners and staff with menus:read.
Only item titles translate. Use Menu item translations with item IDs and the corresponding menu and translation scopes. Menu names, handles, destinations and structure do not translate.
Endpoints#
Menu limits
Fetch Menu limits, tree caps, and the link-type vocabulary.
Returns field maximum lengths, tree-shape caps (total items, children per parent, nesting depth), pagination caps, and the full link-type vocabulary with per-type value requirements. Build item forms from link_types — the set is server-owned. max_per_store is this store's plan cap, count is how many exist now, and usage is the localized label.
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
List menus
List menus for the store.
Returns paginated menus with item counts. Use search to filter by name or handle. total counts matching menus; store_total counts all menus in the store and can be compared with max_per_store from the limits response. Invalid sort parameters are rejected instead of silently defaulted.
Query parameters
| Name | Type | Description |
|---|---|---|
page
|
integer |
Page number. Example: 1 |
limit
|
integer |
Page size, capped by Example: 20 |
search
|
string |
Search menus by name or handle. Example: main |
sort_by
|
string |
Sort column: name, created_at, or updated_at.
Allowed values
namecreated_atupdated_atExample: created_at |
sort_direction
|
string |
asc or desc.
Allowed values
ascdescExample: asc |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Errors
-
400MENU_SEARCH_TOO_LONGSearch exceeds the Menu contract max length.
-
400MENU_LIST_INVALID_SORT_BYSort column is not in the Menu sort vocabulary.
-
400MENU_LIST_INVALID_SORT_DIRECTIONSort direction is not asc or desc.
Get a menu
Fetch one menu with its full item tree.
Returns the menu, its revision (send it back when saving), and its nested item tree. An item whose linked resource shoppers cannot open carries link_status: unpublished (storefronts leave it out until it is published) or missing (deleted; the link is cleaned up shortly). Use include_items=false for metadata and a revision at any stored size. Full reads are limited to 4 MiB of serialized JSON; larger reads return MENU_PAGE_ITEMS_REQUIRED. Page one level at a time using the items endpoint.
Path parameters
| Name | Type | Description |
|---|---|---|
menuId
required
|
uuid |
Menu ID. Example: 9a7f04c2-6f3e-4b58-a2ce-b9d3f43f6f10 |
Query parameters
| Name | Type | Description |
|---|---|---|
include_items
|
boolean |
Set false to read metadata, item_count and revision without the tree. Example: false |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Errors
-
400STORE_API_INVALID_MENU_IDMenu id is not a valid UUID.
-
404NOT_FOUNDMenu (or item) does not exist in this store.
-
413MENU_PAGE_ITEMS_REQUIREDFull menu exceeds the serialized byte budget; read metadata and page items.
List menu items
List one level of a menu's item tree.
Returns one paginated level — root items, or the children of parent_id. Each node carries child_count and exact subtree_count, including unloaded descendants. Use sibling IDs (before_id/after_id) for positional moves; page indexes are not global ranks. Fetch the next page only when has_more is true.
Path parameters
| Name | Type | Description |
|---|---|---|
menuId
required
|
uuid |
Menu ID. Example: 9a7f04c2-6f3e-4b58-a2ce-b9d3f43f6f10 |
Query parameters
| Name | Type | Description |
|---|---|---|
parent_id
|
uuid |
Parent item id. Omit for root items. Example: 1d2b7c44-93cf-4f2f-9f6a-5f4a1e2ce8d0 |
page
|
integer |
Page number within the sibling level. Example: 1 |
limit
|
integer |
Page size, capped by Example: 20 |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Errors
-
400STORE_API_INVALID_MENU_IDMenu id is not a valid UUID.
-
404NOT_FOUNDMenu (or item) does not exist in this store.
Search menu items
Search items across the whole menu.
Finds items by title or link label anywhere in the tree. Each hit includes its root-to-parent ancestor_ids and ancestor_titles so clients can expand-to-reveal without walking the tree.
Path parameters
| Name | Type | Description |
|---|---|---|
menuId
required
|
uuid |
Menu ID. Example: 9a7f04c2-6f3e-4b58-a2ce-b9d3f43f6f10 |
Query parameters
| Name | Type | Description |
|---|---|---|
q
|
string |
Search query over item titles and link labels. An empty query returns an empty list. Example: shop |
page
|
integer |
Page number. Example: 1 |
limit
|
integer |
Page size, capped by Example: 20 |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Errors
-
400STORE_API_INVALID_MENU_IDMenu id is not a valid UUID.
-
404NOT_FOUNDMenu (or item) does not exist in this store.
Create a menu
Create a menu (name + optional handle).
Creates an empty menu. handle is optional — when omitted it is generated from the name and auto-uniquified; a provided handle that collides is rejected. Fires menus/create.
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Body parameters
| Name | Type | Description |
|---|---|---|
name
required
|
string |
Menu name. Example: Main Menu |
handle
|
string |
Optional handle. Omit to generate from the name (auto-uniquified). A provided duplicate is rejected. Example: main-menu |
Errors
-
400MENU_NAME_REQUIREDName is empty after normalization.
-
400MENU_NAME_TOO_LONGName exceeds the Menu contract max length.
-
400MENU_HANDLE_INVALID_FORMATHandle is not slug-shaped (lowercase words joined by hyphens).
-
400MENU_HANDLE_TOO_LONGHandle exceeds the Menu contract max length.
-
400MENU_PLAN_LIMIT_REACHEDThis store is at its plan menu cap.
-
404NOT_FOUNDMenu does not exist in this store.
-
409MENU_HANDLE_DUPLICATEHandle already exists in this store.
-
409MENU_HANDLE_RESERVEDAnother menu reserves this historical handle.
-
409MENU_HANDLE_ALIAS_LIMITMenu has 20 historical handles; retain its handle or reuse one of its own reservations.
Update a menu
Rename a menu or change its handle.
Updates provided fields; omitted fields are unchanged. A handle change is validated and checked for uniqueness. This metadata response includes the new revision and items: null. Read the menu to retrieve its tree. Emits menus/update.
Path parameters
| Name | Type | Description |
|---|---|---|
menuId
required
|
uuid |
Menu ID. Example: 9a7f04c2-6f3e-4b58-a2ce-b9d3f43f6f10 |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Body parameters
| Name | Type | Description |
|---|---|---|
name
|
string |
New menu name. Example: Footer Menu |
handle
|
string |
New handle. Must be unique and slug-shaped. Example: footer-menu |
Errors
-
400MENU_NAME_REQUIREDName is empty after normalization.
-
400MENU_NAME_TOO_LONGName exceeds the Menu contract max length.
-
400MENU_HANDLE_INVALID_FORMATHandle is not slug-shaped (lowercase words joined by hyphens).
-
400MENU_HANDLE_TOO_LONGHandle exceeds the Menu contract max length.
-
400MENU_PLAN_LIMIT_REACHEDThis store is at its plan menu cap.
-
404NOT_FOUNDMenu does not exist in this store.
-
409MENU_HANDLE_DUPLICATEHandle already exists in this store.
-
409MENU_HANDLE_RESERVEDAnother menu reserves this historical handle.
-
409MENU_HANDLE_ALIAS_LIMITMenu has 20 historical handles; retain its handle or reuse one of its own reservations.
Save a menu atomically
Save name, handle, and item changes as one atomic batch.
Save metadata and item changes together, all or nothing. Send the revision from the menu you edited; if the menu changed since, the save is rejected with MENU_SAVE_CONFLICT so nobody's changes are overwritten — fetch it again and reapply. Send exactly one of items or ops. With items, send the complete tree: keep existing item IDs to preserve translations, omit items to delete them, and omit IDs for new items. An empty items array clears the tree. Full request/response JSON has a 4 MiB limit. With ops, send ordered create, update, delete, and move operations; an empty array preserves every item. Create a parent before referencing its unique temp_id in a later operation. Linked resources must belong to the store. Cycles and changes that exceed tree limits are rejected. A tree save returns the saved tree; an operations save returns items: null, so read the menu when you need the resulting items. Emits menus/update.
Path parameters
| Name | Type | Description |
|---|---|---|
menuId
required
|
uuid |
Menu ID. Example: 9a7f04c2-6f3e-4b58-a2ce-b9d3f43f6f10 |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Body parameters
| Name | Type | Description |
|---|---|---|
name
required
|
string |
Menu name. Example: Main Menu |
handle
required
|
string |
Menu handle — required on save. Example: main-menu |
revision
required
|
string |
The Example: 2026-01-05T06:30:00.123456Z |
items
|
array<object> |
The FULL item tree. Each node: optional Example: [{"title": "Shop", "link_type": "shop", "children": []}] |
ops
|
array<object> |
Ordered operations instead of |
Errors
-
400MENU_NAME_REQUIREDName is empty after normalization.
-
400MENU_NAME_TOO_LONGName exceeds the Menu contract max length.
-
400MENU_HANDLE_INVALID_FORMATHandle is not slug-shaped (lowercase words joined by hyphens).
-
400MENU_HANDLE_TOO_LONGHandle exceeds the Menu contract max length.
-
400MENU_PLAN_LIMIT_REACHEDThis store is at its plan menu cap.
-
404NOT_FOUNDMenu does not exist in this store.
-
409MENU_HANDLE_DUPLICATEHandle already exists in this store.
-
409MENU_HANDLE_RESERVEDAnother menu reserves this historical handle.
-
409MENU_HANDLE_ALIAS_LIMITMenu has 20 historical handles; retain its handle or reuse one of its own reservations.
-
413MENU_PAGE_ITEMS_REQUIREDFull menu exceeds the serialized byte budget; use paginated reads and smaller operation batches.
-
400MENU_HANDLE_REQUIREDHandle is required on the save path.
-
400MENU_REVISION_REQUIREDThe save did not send the revision it was edited from.
-
409MENU_SAVE_CONFLICTThe menu changed after the given revision; fetch it again and reapply the changes.
-
400MENU_TOO_MANY_ITEMSThe tree exceeds the total item cap.
-
400MENU_TOO_MANY_CHILDRENA sibling group exceeds the children-per-parent cap.
-
400MENU_ITEM_NESTING_DEPTH_REACHEDThe tree exceeds the nesting-depth cap.
-
400MENU_LINK_PRODUCT_NOT_FOUNDA product link references an id that does not exist (each linkable resource has matching *_ID_INVALID and *_NOT_FOUND codes: product, category, vendor, brand, collection, blog post, blog category, page).
-
400MENU_LINK_POLICY_TYPE_INVALIDA policy link's value is not a valid policy type.
Create a menu item
Add one item to a menu.
Creates one item. link_type comes from the limits vocabulary; types with a target require link_value (ids are existence-validated). parent_id places it in the tree, subject to the nesting-depth cap. Fires menus/update.
Path parameters
| Name | Type | Description |
|---|---|---|
menuId
required
|
uuid |
Menu ID. Example: 9a7f04c2-6f3e-4b58-a2ce-b9d3f43f6f10 |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Body parameters
| Name | Type | Description |
|---|---|---|
title
required
|
string |
Item title. Example: Blue shirt |
link_type
required
|
string |
Link type from the limits vocabulary. Example: product |
link_value
|
string |
Target reference for types that require one — a resource id, policy type, or custom URL. Existence-validated. Example: 42 |
link_label
|
string |
Display label for the linked resource. Example: Blue shirt |
parent_id
|
uuid |
Parent item id. Omit for a root item. Subject to the nesting-depth cap. Example: 1d2b7c44-93cf-4f2f-9f6a-5f4a1e2ce8d0 |
open_in_new_tab
|
boolean |
Open the link in a new tab. Example: false |
sort_order
|
integer |
Position within the sibling group. Omit to append. Example: 0 |
Errors
-
400STORE_API_INVALID_MENU_IDMenu id is not a valid UUID.
-
400MENU_ITEM_TITLE_REQUIREDItem title is empty after normalization.
-
400MENU_ITEM_TITLE_TOO_LONGItem title exceeds the contract max length.
-
400MENU_ITEM_LINK_TYPE_INVALIDLink type is not in the server vocabulary.
-
400MENU_ITEM_LINK_VALUE_REQUIREDThe link type requires a link_value.
-
400MENU_ITEM_LIMIT_REACHEDThe menu is at its total item cap.
-
400MENU_ITEM_PARENT_NOT_FOUNDParent_id does not exist in this menu.
-
400MENU_ITEM_NESTING_DEPTH_REACHEDPlacement exceeds the nesting-depth cap.
-
400MENU_ITEM_DESCENDANT_PARENTAn item cannot be moved under itself or its own descendant.
-
400MENU_TOO_MANY_CHILDRENThe sibling group exceeds the children-per-parent cap.
-
404NOT_FOUNDMenu or item does not exist in this store.
-
400MENU_LINK_PRODUCT_NOT_FOUNDA product link references an id that does not exist (each linkable resource has matching *_ID_INVALID and *_NOT_FOUND codes: product, category, vendor, brand, collection, blog post, blog category, page).
-
400MENU_LINK_POLICY_TYPE_INVALIDA policy link's value is not a valid policy type.
Update a menu item
Update one item's fields or placement.
Updates provided fields. Switching to a link type without a target clears link_value. parent_id reparents the item (cycle- and depth-guarded); an empty string moves it to the root. Fires menus/update.
Path parameters
| Name | Type | Description |
|---|---|---|
menuId
required
|
uuid |
Menu ID. Example: 9a7f04c2-6f3e-4b58-a2ce-b9d3f43f6f10 |
itemId
required
|
uuid |
Menu item id. Example: 1d2b7c44-93cf-4f2f-9f6a-5f4a1e2ce8d0 |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Body parameters
| Name | Type | Description |
|---|---|---|
title
|
string |
New item title. Example: Shop all |
link_type
|
string |
New link type. Switching to a type without a target clears Example: collection_list |
link_value
|
string |
New target reference. Existence-validated. |
link_label
|
string |
New display label. |
parent_id
|
string |
New parent item id; empty string moves the item to the root. Cycle- and depth-guarded. |
open_in_new_tab
|
boolean |
Open the link in a new tab. Example: true |
Errors
-
400STORE_API_INVALID_MENU_IDMenu id is not a valid UUID.
-
400MENU_ITEM_TITLE_REQUIREDItem title is empty after normalization.
-
400MENU_ITEM_TITLE_TOO_LONGItem title exceeds the contract max length.
-
400MENU_ITEM_LINK_TYPE_INVALIDLink type is not in the server vocabulary.
-
400MENU_ITEM_LINK_VALUE_REQUIREDThe link type requires a link_value.
-
400MENU_ITEM_LIMIT_REACHEDThe menu is at its total item cap.
-
400MENU_ITEM_PARENT_NOT_FOUNDParent_id does not exist in this menu.
-
400MENU_ITEM_NESTING_DEPTH_REACHEDPlacement exceeds the nesting-depth cap.
-
400MENU_ITEM_DESCENDANT_PARENTAn item cannot be moved under itself or its own descendant.
-
400MENU_TOO_MANY_CHILDRENThe sibling group exceeds the children-per-parent cap.
-
404NOT_FOUNDMenu or item does not exist in this store.
-
400MENU_LINK_PRODUCT_NOT_FOUNDA product link references an id that does not exist (each linkable resource has matching *_ID_INVALID and *_NOT_FOUND codes: product, category, vendor, brand, collection, blog post, blog category, page).
-
400MENU_LINK_POLICY_TYPE_INVALIDA policy link's value is not a valid policy type.
Delete a menu item
Delete one item and its descendants.
Deletes the item and all of its children. Fires menus/update.
Path parameters
| Name | Type | Description |
|---|---|---|
menuId
required
|
uuid |
Menu ID. Example: 9a7f04c2-6f3e-4b58-a2ce-b9d3f43f6f10 |
itemId
required
|
uuid |
Menu item id. Example: 1d2b7c44-93cf-4f2f-9f6a-5f4a1e2ce8d0 |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Errors
-
400STORE_API_INVALID_MENU_IDMenu id is not a valid UUID.
-
404NOT_FOUNDMenu (or item) does not exist in this store.
Delete a menu
Delete a menu and all of its items.
Deletes the menu and all of its items. Fires menus/delete.
Path parameters
| Name | Type | Description |
|---|---|---|
menuId
required
|
uuid |
Menu ID. Example: 9a7f04c2-6f3e-4b58-a2ce-b9d3f43f6f10 |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Errors
-
400STORE_API_INVALID_MENU_IDMenu id is not a valid UUID.
-
404NOT_FOUNDMenu (or item) does not exist in this store.
Fields#
Field |
Type |
Create |
Update |
Required |
Translatable |
|---|---|---|---|---|---|
|
string |
Yes |
Yes |
create |
No |
|
string |
Yes |
Yes |
No |
|
|
integer |
No |
No |
No |
|
|
string (RFC 3339) |
No |
No |
No |
|
|
string (RFC 3339) |
No |
No |
No |
Webhook events#
Subscribe to these events to keep your integration in sync with menus. Each event shows the required Store API scope and store permission, the available payload fields, and an example payload. Subscription setup is validated against those permissions; delivery is always restricted to the subscription's store.
Menu created
menus/create
App scope: read_menus
Staff permission: menus:write
Fired when a navigation menu is created.
Payload fields
| Field | Type | Description |
|---|---|---|
menu.id |
string |
Always present |
menu.name |
string |
Selectable in the Payload builder |
menu.handle |
string |
Selectable in the Payload builder |
menu.item_count |
integer |
Selectable in the Payload builder |
menu.created_at |
string |
Selectable in the Payload builder |
menu.updated_at |
string |
Selectable in the Payload builder |
Menu updated
menus/update
App scope: read_menus
Staff permission: menus:write
Fired when a navigation menu or one of its items is updated.
Payload fields
| Field | Type | Description |
|---|---|---|
menu.id |
string |
Always present |
menu.name |
string |
Selectable in the Payload builder |
menu.handle |
string |
Selectable in the Payload builder |
menu.item_count |
integer |
Selectable in the Payload builder |
menu.created_at |
string |
Selectable in the Payload builder |
menu.updated_at |
string |
Selectable in the Payload builder |
Menu deleted
menus/delete
App scope: read_menus
Staff permission: menus:write
Fired when a navigation menu is deleted.
Payload fields
| Field | Type | Description |
|---|---|---|
menu.id |
string |
Always present |
menu.name |
string |
Selectable in the Payload builder |
menu.handle |
string |
Selectable in the Payload builder |