data-slots=text
data-backgroundColor=green
SaaS only

Implement categories on the storefront

Use the following API operations to manage categories for Commerce projects that use the Merchandising Services composable catalog data model:

data-variant=warning
data-slots=text
For Commerce sites with an Adobe Commerce as a Cloud Service or an Adobe Commerce on Cloud infrastructure or on-premises backend, manage categories configuration from the Commerce Admin, and use the categories query available in the Catalog Service GraphQL API to manage categories.

Category types

The navigation query, categoryTree query, searchCategory query, and categories field on product queries each return data shaped for a specific use case. Navigation, tree, and search results use types that implement the CategoryViewV2 interface, which defines the two required fields shared by every category: slug and name. For complete field details, see CategoryViewV2 in the Merchandising Services GraphQL API reference.

  1. CategoryNavigationView — For menu rendering and navigation
  2. CategoryProductView — For category data returned with product queries
  3. CategoryTreeView — For hierarchical category management, rich category pages, and each matching category from the searchCategory query

CategoryNavigationView type

The CategoryNavigationView type implements CategoryViewV2 and provides category data optimized for storefront navigation. It contains:

Use this type to render top menus, dropdowns, and mobile navigation.

Type definition

type CategoryNavigationView implements CategoryViewV2 {
    slug: String!
    name: String!
    children: [CategoryNavigationView]
}

For complete field details, see CategoryNavigationView in the Merchandising Services GraphQL API reference.

See the Navigation query examples section for example queries and responses using this type.

CategoryProductView type

The CategoryProductView type implements CategoryViewV2 and provides category data within product query responses. Each product's categories field returns a list of CategoryProductView objects containing:

Use this type to render breadcrumbs, filter by category, or display category context on product detail pages.

Type definition

type CategoryProductView implements CategoryViewV2 {
    name: String!
    slug: String!
    level: Int!
    parents: [CategoryProductView!]
}

The parents field is self-referencing—each parent entry is itself a CategoryProductView with its own name, slug, level, and parents. This allows you to reconstruct the full breadcrumb path for any category a product belongs to.

For complete field details, see CategoryProductView in the Merchandising Services GraphQL API reference.

See the Products query with categories examples section for example queries and responses using this type.

CategoryTreeView type

The CategoryTreeView type implements CategoryViewV2 and provides the richest category data. The categoryTree query returns this type directly. The searchCategory query returns the same type inside SearchCategoryResultPage.items, so search hits can use the same fields as tree results.

It contains:

Use this type for rich category landing pages, SEO-driven content, CMS administration, and displaying full category details for searchCategory matches (for example, typeahead previews or picker dialogs).

Type definition

type CategoryTreeView implements CategoryViewV2 {
    slug: String!
    name: String!
    level: Int
    parentSlug: String
    childrenSlugs: [String]
    description: String
    metaTags: CategoryMetaTags
    images: [CategoryImage]
}

type CategoryMetaTags {
    title: String
    description: String
    keywords: [String]
}

type CategoryImage {
    url: String!
    label: String
    roles: [String]
    customRoles: [String]
}

For complete field details, including the CategoryMetaTags and CategoryImage types, see CategoryTreeView in the Merchandising Services GraphQL API reference.

See the CategoryTree query examples and searchCategory query examples sections for example queries and responses using this type.

Limitations and considerations

Choose the right query for the use case

The navigation query returns a maximum of four levels of nested categories. Nesting children beyond four levels in your query returns no additional data. Design your category hierarchy and query depth accordingly.

categoryTree depth and discovery behavior

The depth argument behaves differently depending on whether you pass starting slugs:

Pass slugs when you need a specific branch rather than a level-capped slice from the top of the tree. The family argument is optional on categoryTree; supply it when you must limit results to one category family.

Optional fields add overhead

The description, metaTags, and images fields on categoryTree are optional. Exclude them when building navigation or hierarchy views that do not need descriptive content or SEO metadata.

Limit categoryTree depth

Pass the depth parameter to categoryTree to avoid fetching deeper levels than you need. Remember that its meaning depends on whether slugs are present; see categoryTree depth and discovery behavior.

Target specific subtrees

Pass the slugs parameter to categoryTree to fetch only the branches you need rather than the entire tree.

The navigation query signature:

type Query {
    navigation(family: String!): [CategoryNavigationView]
}

The family parameter is required and specifies which category family to retrieve. The query returns the full hierarchy for that family in a single request.

Retrieve basic top menu navigation

When you only need identity fields for the root of a category family—for example, a simple top bar or a single entry point before loading deeper levels—you can query navigation with slug and name only. The following example requests the sports family and returns that root node without selecting children, so the response stays small and easy to cache.

data-slots=heading, code
data-repeat=2
data-languages=JSON
Request:
query TopLevelNavigation {
  navigation(family: "sports") {
    slug
    name
  }
}
Response:
{
  "data": {
    "navigation": [
      {
        "slug": "sports",
        "name": "Category of sports"
      }
    ]
  },
}

The response returns a single root node with no children.

Sports

Retrieve multi-level menu navigation

Storefront menus that expand into submenus or mega panels need nested children on each CategoryNavigationView. The following example nests children selection sets through three levels (within the navigation query's four-level cap). The response illustrates how indoor and outdoor branches attach under the same root so you can render a full hierarchy in one round trip.

data-slots=heading, code
data-repeat=2
data-languages=JSON
Request:
query GetFullMenuNavigation {
    navigation(family: "sports") {
        slug
        name
        children {
            slug
            name
            children {
                slug
                name
                children {
                    slug
                    name
                }
            }
        }
    }
}
Response:
{
  "data": {
    "navigation": [
      {
        "slug": "sports",
        "name": "Category of sports",
        "children": [
          {
            "slug": "sports/indoors",
            "name": "Sports to be played indoors",
            "children": [
              {
                "slug": "sports/indoors/pilates",
                "name": "Pilates sport",
                "children": []
              }
            ]
          },
          {
            "slug": "sports/outdoors",
            "name": "Sports to be played outdoors",
            "children": [
              {
                "slug": "sports/outdoors/golf",
                "name": "Go sport",
                "children": []
              }
            ]
          }
        ]
      }
    ]
  }
}

The response returns a three-level nested hierarchy:

Sports
└── Indoors
    └── Pilates
└── Outdoors
    └── Golf

Products query with categories examples

type ProductView {
    categories(family: String): [CategoryProductView]
}

The categories field is available on product types such as ProductView. Use the optional family parameter to return only categories from a specific category family. When omitted, categories from all families are returned.

Retrieve product categories with breadcrumb ancestors

Product detail pages often need the category path from the root down to the product. The following example queries products by SKU and requests categories for the clothing family with name, slug, level, and each category's parents, so you can order ancestors by level and render a breadcrumb trail.

data-slots=heading, code
data-repeat=2
data-languages=JSON
GraphQL request:
query {
    products(skus: ["shorts-red-m"]) {
        name
        sku
        categories(family: "clothing") {
            name
            slug
            level
            parents {
                name
                slug
                level
            }
        }
    }
}
Response:
{
    "data": {
        "products": [
            {
                "name": "Red Shorts (M)",
                "sku": "shorts-red-m",
                "categories": [
                    {
                        "name": "Shorts",
                        "slug": "men/clothes/shorts",
                        "level": 3,
                        "parents": [
                            {
                                "name": "Men",
                                "slug": "men",
                                "level": 1
                            },
                            {
                                "name": "Clothes",
                                "slug": "men/clothes",
                                "level": 2
                            }
                        ]
                    }
                ]
            }
        ]
    }
}

The parents array provides the full ancestor chain, which you can use to render a breadcrumb path:

Men (level 1) → Clothes (level 2) → Shorts (level 3)
                                      └── product: Red Shorts (M)

Filter product categories by family

A single product can appear in categories from more than one family; returning every family at once is not always what the page needs. The following example uses the same SKU as the breadcrumb sample but passes family: "seasonal" on categories, so only seasonal taxonomy nodes come back. That pattern fits seasonal campaigns, alternate merchandising trees, or any UI that should show one family's context at a time.

data-slots=heading, code
data-repeat=2
data-languages=JSON
Request:
query {
    products(skus: ["shorts-red-m"]) {
        name
        sku
        categories(family: "seasonal") {
            name
            slug
            level
            parents {
                name
                slug
                level
            }
        }
    }
}
Response:
{
    "data": {
        "products": [
            {
                "name": "Red Shorts (M)",
                "sku": "shorts-red-m",
                "categories": [
                    {
                        "name": "Summer Essentials",
                        "slug": "summer/essentials",
                        "level": 2,
                        "parents": [
                            {
                                "name": "Summer",
                                "slug": "summer",
                                "level": 1
                            }
                        ]
                    }
                ]
            }
        ]
    }
}

Without the family filter, the response would include categories from all families the product belongs to—for example, both "Shorts" from the "clothing" family and "Summer Essentials" from the "seasonal" family. The family parameter narrows the result to a single family, which is useful when rendering context-specific navigation or breadcrumbs.

CategoryTree query examples

The categoryTree query signature:

type Query {
    categoryTree(family: String, slugs: [String!], depth: Int): [CategoryTreeView]
}

The family argument is optional. Include it when you need to restrict the tree to one category family.

Retrieve root-level categories

When you call categoryTree without slugs, depth (when set) limits how deep the result goes by category level across the scoped tree; see categoryTree depth and discovery behavior. The following example scopes by family only, omits depth, and asks for slug, name, level, parentSlug, and childrenSlugs. Use this shape to discover top-level categories and their immediate child references before requesting heavier subtrees.

data-slots=heading, code
data-repeat=2
data-languages=JSON
Request:
query GetRootCategories {
    categoryTree(family: "main-catalog") {
        slug
        name
        level
        parentSlug
        childrenSlugs
    }
}
Response:
{
    "data": {
        "categoryTree": [
            {
                "slug": "men",
                "name": "Men's Category",
                "level": 1,
                "parentSlug": "",
                "childrenSlugs": ["men/clothing"]
            },
            {
                "slug": "women",
                "name": "Women's Category",
                "level": 1,
                "parentSlug": "",
                "childrenSlugs": ["women/clothing"]
            }
        ]
    }
}

The flat list represents the root-level categories and their immediate children.

Men's Category (level 1)
└── Men's Clothing (level 2)
Women's Category (level 1)
└── Women's Clothing (level 2)

Retrieve specific category subtree

When you already know which branches matter—such as men's and women's clothing—pass those starting slugs so you do not pull the entire catalog. With slugs present, depth counts from each starting slug (that slug is level 1 of the window). The following example requests two clothing subtrees under main-catalog with depth: 2 and returns nodes with hierarchy fields so you can wire category pages or admin trees for just those paths.

data-slots=heading, code
data-repeat=2
data-languages=JSON
Request:
query GetSpecificCategorySubtree {
    categoryTree(
        family: "main-catalog"
        slugs: ["men/clothing", "women/clothing"]
        depth: 2
    ) {
        slug
        name
        level
        parentSlug
        childrenSlugs
    }
}
Response:
{
    "data": {
        "categoryTree": [
            {
                "slug": "men/clothing",
                "name": "Men's Clothing",
                "level": 2,
                "parentSlug": "men",
                "childrenSlugs": ["men/clothing/tops", "men/clothing/bottoms"]
            },
            {
                "slug": "men/clothing/tops",
                "name": "Men's Tops",
                "level": 3,
                "parentSlug": "men/clothing",
                "childrenSlugs": []
            },
            {
                "slug": "men/clothing/bottoms",
                "name": "Men's Bottoms",
                "level": 3,
                "parentSlug": "men/clothing",
                "childrenSlugs": []
            },
            {
                "slug": "women/clothing",
                "name": "Women's Clothing",
                "level": 2,
                "parentSlug": "women",
                "childrenSlugs": ["women/clothing/tops", "women/clothing/bottoms"]
            },
            {
                "slug": "women/clothing/tops",
                "name": "Women's Tops",
                "level": 3,
                "parentSlug": "women/clothing",
                "childrenSlugs": []
            },
            {
                "slug": "women/clothing/bottoms",
                "name": "Women's Bottoms",
                "level": 3,
                "parentSlug": "women/clothing",
                "childrenSlugs": []
            }
        ]
    }
}

With starting slugs, the depth argument counts levels from each starting slug (the slug itself is level 1). The level field on each CategoryTreeView still reflects that category's absolute position in the full hierarchy for its slug path.

Men's Clothing (level 2)      Women's Clothing (level 2)
├── Men's Tops (level 3)      ├── Women's Tops (level 3)
└── Men's Bottoms (level 3)   └── Women's Bottoms (level 3)

Retrieve category details with metadata and images

Category landing pages usually need more than slugs and labels: copy for the body, <meta> fields for search, and imagery for heroes or thumbnails. The following example targets one slug under clothing and selects description, metaTags (title, description, keywords), and images with url, label, and role fields—enough to render a full SEO-aware category template from a single categoryTree call.

data-slots=heading, code
data-repeat=2
data-languages=JSON
Request:
query CategoryTree {
    categoryTree(slugs: ["men/clothes/shorts"], family: "clothing") {
        slug
        name
        level
        parentSlug
        childrenSlugs
        description
        metaTags {
            title
            description
            keywords
        }
        images {
            url
            label
            roles
            customRoles
        }
    }
}
Response:
{
    "data": {
        "categoryTree": [
            {
                "slug": "men/clothes/shorts",
                "name": "Shorts",
                "level": 3,
                "parentSlug": "men/clothes",
                "childrenSlugs": [],
                "description": "Browse our full range of men's shorts, from casual to athletic styles.",
                "metaTags": {
                    "title": "Men's Shorts",
                    "description": "Shop men's shorts for every occasion",
                    "keywords": [
                        "shorts",
                        "men"
                    ]
                },
                "images": [
                    {
                        "url": "https://example.com/images/shorts.jpg",
                        "label": "Men's shorts collection",
                        "roles": [
                            "BASE",
                            "THUMBNAIL"
                        ],
                        "customRoles": [
                            "special-role"
                        ]
                    }
                ]
            }
        ]
    }
}

searchCategory query examples

The searchCategory query matches category names against a searchTerm and returns a paginated SearchCategoryResultPage.

Each items entry is a CategoryTreeView, so you can reuse the same fields as in categoryTree responses (for example, slug, name, description, and images).

type Query {
    searchCategory(
        searchTerm: String!
        family: String
        pageSize: Int
        currentPage: Int
    ): SearchCategoryResultPage
}

type SearchCategoryResultPage {
    items: [CategoryTreeView!]!
    totalCount: Int!
    pageInfo: PageInfo!
}

type PageInfo {
    currentPage: Int!
    pageSize: Int!
    totalPages: Int!
}

pageSize defaults to 20 and currentPage defaults to 1 (1-based). Use family to limit matches to one category family when needed.

Search categories by name

Shoppers and internal tools often find categories by typing a fragment of the display name rather than browsing the tree. The following example calls searchCategory with searchTerm: "Men" and no family, then requests totalCount, a page of items with slug, name, and level, plus pageInfo for pagination.. The response lists every category whose name matches across the catalog scope your API uses.

data-slots=heading, code
data-repeat=2
data-languages=JSON
Request:
query {
  searchCategory(searchTerm: "Men", pageSize: 20, currentPage: 1) {
    totalCount
    items {
      name
      slug
      parentSlug
      childrenSlugs
    }
  }
}
Response:
{
  "data": {
    "searchCategory": {
      "totalCount": 5,
      "items": [
        {
          "name": "Men",
          "slug": "men",
          "parentSlug": "",
          "childrenSlugs": [
            "men/tops",
            "men/bottoms",
            "men/accessories",
            "men/footwear"
          ]
        },
        {
          "name": "Men Footwear",
          "slug": "men/footwear",
          "parentSlug": "men",
          "childrenSlugs": [
            "men/footwear/sneakers"
          ]
        },
        {
          "name": "Men Accessories",
          "slug": "men/accessories",
          "parentSlug": "men",
          "childrenSlugs": [
            "men/accessories/socks"
          ]
        },
        {
          "name": "Men Bottoms",
          "slug": "men/bottoms",
          "parentSlug": "men",
          "childrenSlugs": [
            "men/bottoms/shorts"
          ]
        },
        {
          "name": "Men Tops test",
          "slug": "men/tops",
          "parentSlug": "men",
          "childrenSlugs": [
            "men/tops/shirts"
          ]
        }
      ]
    }
  },
  "extensions": {
    "request-id": "c1d2e6a3-6671-408f-8674-14aae3ae890f"
  }
}

Search within a family and paginate

Large catalogs can return many name matches; narrowing by family and paging keeps typeaheads and pickers responsive. The following example searches for "tops" inside main-catalog, sets pageSize and currentPage, and returns parentSlug and childrenSlugs on each hit plus pageInfo showing how many pages exist when totalCount exceeds the page size.

data-slots=heading, code
data-repeat=2
data-languages=JSON
Request:
query SearchCategoriesInFamily {
    searchCategory(
        searchTerm: "tops"
        family: "main-catalog"
        pageSize: 10
        currentPage: 1
    ) {
        totalCount
        items {
            slug
            name
            parentSlug
            childrenSlugs
        }
        pageInfo {
            currentPage
            pageSize
            totalPages
        }
    }
}
Response:
{
    "data": {
        "searchCategory": {
            "totalCount": 15,
            "items": [
                {
                    "slug": "men/clothing/tops",
                    "name": "Men's Tops",
                    "parentSlug": "men/clothing",
                    "childrenSlugs": []
                }
            ],
            "pageInfo": {
                "currentPage": 1,
                "pageSize": 10,
                "totalPages": 2
            }
        }
    }
}

For additional information, see searchCategory and PageInfo in the Merchandising Services GraphQL API reference.

Query quick reference

Use case
Query
Type
Storefront menus, dropdowns, mobile navigation
navigation
CategoryNavigationView
Category hierarchy management, landing pages with SEO metadata and images
categoryTree
CategoryTreeView
Breadcrumbs and category context on product pages
products (categories field)
CategoryProductView
Search or browse categories by name (typeahead, admin pickers)
searchCategory
CategoryTreeView (in SearchCategoryResultPage.items)