Menus require menus scopes: reads use read_menus, writes use write_menus. A menu is a named, handle-addressed tree of items; each item links to a store resource (product, category, collection, page, blog post, vendor, brand, policy), a fixed destination (home, shop, cart, wishlist, account, the list pages), a custom URL, or is a non-navigable label heading a dropdown. Fetch /menus/limits first — the link-type vocabulary, per-type value requirements, and every tree cap (total items, children per parent, nesting depth) come from there, never hardcoded.
Item reads are built for scale: /menus/{menuId}/items pages one tree level at a time with per-node child counts, and /menus/{menuId}/items/search finds items anywhere in the tree with their ancestor paths. Writes are strict — cross-resource link targets are existence-validated in one batched pass, tree caps are enforced server-side, and reparenting is cycle- and depth-guarded. The atomic save (PUT /menus/{menuId}/save) applies name, handle, and item changes all-or-nothing: send the full items tree (existing item ids update in place so item-title translations survive) or a dirty ops buffer that stays O(changes) for very large menus. Item titles translate through the translations surface as the menu_item resource.
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.
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. 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 and its nested item tree. For very large menus prefer the paginated items endpoint and page one level at a time.
Path parameters
| Name | Type | Description |
|---|---|---|
id
required
|
uuid |
Menu id. Example: 5ff7d4ea-3250-4a4d-9d26-07f64d917d31 |
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.
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 so clients can render expand affordances without fetching children. This is how the admin editor scales to the max item cap.
Path parameters
| Name | Type | Description |
|---|---|---|
id
required
|
uuid |
Menu id. Example: 5ff7d4ea-3250-4a4d-9d26-07f64d917d31 |
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 |
|---|---|---|
id
required
|
uuid |
Menu id. Example: 5ff7d4ea-3250-4a4d-9d26-07f64d917d31 |
Query parameters
| Name | Type | Description |
|---|---|---|
q
required
|
string |
Search query over item titles and link labels. 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_LIMIT_REACHEDThe store is at its menu cap.
-
404NOT_FOUNDMenu does not exist in this store.
-
409MENU_HANDLE_DUPLICATEHandle already exists in this store.
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. Items are managed through the item endpoints or the atomic save. Fires menus/update.
Path parameters
| Name | Type | Description |
|---|---|---|
id
required
|
uuid |
Menu id. Example: 5ff7d4ea-3250-4a4d-9d26-07f64d917d31 |
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_LIMIT_REACHEDThe store is at its menu cap.
-
404NOT_FOUNDMenu does not exist in this store.
-
409MENU_HANDLE_DUPLICATEHandle already exists in this store.
Save a menu atomically
Save name, handle, and item changes as one atomic batch.
The whole-menu save. Send items (the full tree — existing item ids are updated in place so item-title translations survive, missing ids are deleted, new items are created) OR ops (a dirty-ops buffer of create/update/delete/move operations, O(changes) instead of O(tree)). Cross-resource links (products, categories, collections, pages, blog posts, blog categories, vendors, brands, and policies) are existence-validated in one batched pass. Fires one compact menus/update.
Path parameters
| Name | Type | Description |
|---|---|---|
id
required
|
uuid |
Menu id. Example: 5ff7d4ea-3250-4a4d-9d26-07f64d917d31 |
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 |
items
|
array<object> |
The FULL item tree. Each node: optional Example: [{"title": "Shop", "link_type": "shop", "children": []}] |
ops
|
array<object> |
Dirty-ops buffer instead of Example: [{"kind": "create", "temp_id": "t1", "title": "Sale", "link_type": "shop"}] |
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_LIMIT_REACHEDThe store is at its menu cap.
-
404NOT_FOUNDMenu does not exist in this store.
-
409MENU_HANDLE_DUPLICATEHandle already exists in this store.
-
400MENU_HANDLE_REQUIREDHandle is required on the save path.
-
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 |
|---|---|---|
id
required
|
uuid |
Menu id. Example: 5ff7d4ea-3250-4a4d-9d26-07f64d917d31 |
Headers
| Name | Type | Description |
|---|---|---|
Authorization
required
|
string |
Bearer Store API token. |
Body parameters
| Name | Type | Description |
|---|---|---|
title
required
|
string |
Item title. Example: Shop |
link_type
required
|
string |
Link type from the limits vocabulary. Example: shop |
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 |
|---|---|---|
id
required
|
uuid |
Menu id. Example: 5ff7d4ea-3250-4a4d-9d26-07f64d917d31 |
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 |
|---|---|---|
id
required
|
uuid |
Menu id. Example: 5ff7d4ea-3250-4a4d-9d26-07f64d917d31 |
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 |
|---|---|---|
id
required
|
uuid |
Menu id. Example: 5ff7d4ea-3250-4a4d-9d26-07f64d917d31 |
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 |
Kind |
Create |
Update |
Required |
Translatable |
|---|---|---|---|---|---|
|
text |
Yes |
Yes |
create |
No |
|
slug |
Yes |
Yes |
No |
|
|
integer |
No |
No |
No |
|
|
datetime |
No |
No |
No |
|
|
datetime |
No |
No |
No |
Webhook events#
Menu webhook events are declared by the Menu resource contract. The event cards below include the topic, subscription scope, store permission, payload schema, and sample payload.
Menu created
menus/create
write_menus
menus:write
Fired when a navigation menu is created.
Payload fields
| Field | Type | Description |
|---|---|---|
menu.id |
string |
Always present |
menu.name |
string |
Can be removed/added via the Payload builder |
menu.handle |
string |
Can be removed/added via the Payload builder |
menu.item_count |
integer |
Can be removed/added via the Payload builder |
menu.created_at |
string |
Can be removed/added via the Payload builder |
menu.updated_at |
string |
Can be removed/added via the Payload builder |
Menu updated
menus/update
write_menus
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 |
Can be removed/added via the Payload builder |
menu.handle |
string |
Can be removed/added via the Payload builder |
menu.item_count |
integer |
Can be removed/added via the Payload builder |
menu.created_at |
string |
Can be removed/added via the Payload builder |
menu.updated_at |
string |
Can be removed/added via the Payload builder |
Menu deleted
menus/delete
write_menus
menus:write
Fired when a navigation menu is deleted.
Payload fields
| Field | Type | Description |
|---|---|---|
menu.id |
string |
Always present |
menu.name |
string |
Can be removed/added via the Payload builder |
menu.handle |
string |
Can be removed/added via the Payload builder |