Supercommerce API Docs
Store API

Navigation Module — Storefront

HTTP surface for reading admin-managed navigation menus. A menu is a nested tree of links (header bar, mobile drawer, footer column) addressed by slug and filtered to the caller's platform.

HTTP surface for reading admin-managed navigation menus. A menu is a nested tree of links — the desktop header bar, the mobile drawer, a footer column, an app tab bar — addressed by slug and filtered to the caller's platform.

Source: api-modules/navigation/src/controllers/store-menu.controller.ts.

Admin CRUD for menus and their items is in admin/navigation.md. The storefront fetches a whole tree in one call and renders it directly.


Conventions

Authentication

EndpointAuth
GET /store/menusnone (public)
GET /store/menus/slug/:slugnone (public)

Navigation is public marketing content — no session, no PII.

Response envelope

{
  "data": <payload>,
  "message": "Success",
  "statusCode": 200
}

Error envelope

statusCodeerrorCode examples
404NOT_FOUND
500INTERNAL_SERVER_ERROR, DATABASE_ERROR

Platform filtering

Every menu and every item carries platform (APP / WEB / BOTH, default BOTH). Pass ?platform=APP|WEB — or the x-platform header, which the query param overrides — to keep only rows whose platform is BOTH or the requested one.

Filtering applies to the tree, not to a flat list: an item whose parent is filtered out disappears with it, along with the rest of that subtree. The same rule applies to isActive — hiding a parent hides everything nested under it.


Domain types

type MenuNode = {
  id: string;
  label: string;
  url: string | null;                  // resolved destination; null for a heading that only groups children
  linkType: "CUSTOM" | "CATEGORY" | "TAG" | "BRAND" | "PRODUCT" | "PAGE";
  targetId: string | null;             // the linked entity's id for a typed link
  badge: string | null;                // small pill next to the label, e.g. "Trending"
  image: string | null;                // storage keyresolve via storage CDN
  platform: "APP" | "WEB" | "BOTH";
  openInNewTab: boolean;
  metadata: Record<string, unknown> | null;  // render hints, e.g. { "column": 2, "badgeVariant": "highlight" }
  children: MenuNode[];                // ordered; empty for a leaf
};

For a typed link (linkType other than CUSTOM), url is recomputed on every read from the target entity's current slug and the operator's store/storefront_urls path patterns — so renaming a category doesn't break the nav. If the target has been deleted, the stored url snapshot is returned instead.

type MenuResponse = {
  id: string;
  title: string;
  slug: string;                        // lowercase alnum + hyphens, e.g. "header"
  platform: "APP" | "WEB" | "BOTH";
  isActive: boolean;
  metadata: Record<string, unknown> | null;
  createdAt: string;                   // ISO
  updatedAt: string;                   // ISO
};

MenuResponse plus items: MenuNode[] — the root entries, ordered.


Endpoints

GET /store/menus — List active menus

Returns every active menu without items, so a client can discover what exists instead of hardcoding slugs.

Query params

NameNotes
platformAPP / WEB / BOTH, optional. Absent or BOTH returns every active menu.

Headers

NameNotes
x-platformFallback platform filter used only when the platform query param is omitted.

Response 200MenuResponse[].


GET /store/menus/slug/:slug — Fetch one menu as a tree

Returns the menu plus its nested items, each level ordered by the admin's arrangement. Only active items matching the platform filter are included.

Path params

NameNotes
slugMenu slug (lowercase alnum + hyphens)

Query params / headers — same platform / x-platform pair as the list endpoint.

Response 200MenuTreeResponse.

{
  "data": {
    "id": "01J9...",
    "title": "Main Header",
    "slug": "header",
    "platform": "BOTH",
    "isActive": true,
    "metadata": null,
    "createdAt": "2026-08-01T08:00:00.000Z",
    "updatedAt": "2026-09-01T08:00:00.000Z",
    "items": [
      {
        "id": "01J9...",
        "label": "Skin",
        "url": null,
        "linkType": "CUSTOM",
        "targetId": null,
        "badge": null,
        "image": null,
        "platform": "BOTH",
        "openInNewTab": false,
        "metadata": null,
        "children": [
          {
            "id": "01J9...",
            "label": "Shop by Concern",
            "url": null,
            "linkType": "CUSTOM",
            "targetId": null,
            "badge": null,
            "image": null,
            "platform": "BOTH",
            "openInNewTab": false,
            "metadata": { "column": 0 },
            "children": [
              {
                "id": "01J9...",
                "label": "Acne",
                "url": "/product-category/acne",
                "linkType": "CATEGORY",
                "targetId": "01J9...",
                "badge": "Trending",
                "image": null,
                "platform": "BOTH",
                "openInNewTab": false,
                "metadata": null,
                "children": []
              }
            ]
          }
        ]
      }
    ]
  },
  "message": "Success",
  "statusCode": 200
}

Errors

StatusCodeWhen
404NOT_FOUNDNo menu matches the slug, the menu is inactive, or it is scoped to another platform

Which menu goes where

The storefront doesn't hardcode slugs — it reads them from the public store/navigation settings group (GET /store/settings/navigation):

KeyDefaultSlot
header_menu_slugheaderDesktop header navigation bar
mobile_menu_slugmobileMobile navigation drawer

Point both keys at the same slug to share one tree across desktop and mobile.


Caching

Reads are cached in Redis and served behind the storefront's own ISR cache, tagged menus and menu/<slug>. Every admin write flushes both, so an edit is live without waiting for the TTL.


  • settingsstore/navigation (which menu each slot renders) and store/storefront_urls (the path patterns typed links resolve against). See store/settings.md.
  • dynamic-link — flat, image-first tile collections for content slots. Use that for a promo grid; use this for hierarchical navigation. See store/dynamic-link.md.

On this page