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:
Create category data using the
categoriesoperations available in the Data Ingestion REST API, and using theproductsoperations to manage product category assignments.Retrieve category navigation and hierarchy data using the
navigationandcategoryTreequeries. Both queries take afamilyargument where applicable; oncategoryTree,familyis optional—pass it to scope results when your catalog uses multiple category families.Search categories by name with optional family filtering and pagination using the
searchCategoryquery.Retrieve category context for products — such as breadcrumbs — using the
categoriesfield on product queries.
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.
- CategoryNavigationView — For menu rendering and navigation
- CategoryProductView — For category data returned with product queries
- CategoryTreeView — For hierarchical category management, rich category pages, and each matching category from the
searchCategoryquery
CategoryNavigationView type
The CategoryNavigationView type implements CategoryViewV2 and provides category data optimized for storefront navigation. It contains:
- name and slug — Category identity
- children — Nested subcategories for building the full menu hierarchy in a single query
Use this type to render top menus, dropdowns, and mobile navigation.
Type definition
Copied to your clipboardtype 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:
- name and slug — Category identity
- level — Position in the hierarchy
- parents — Full chain of ancestor categories
Use this type to render breadcrumbs, filter by category, or display category context on product detail pages.
Type definition
Copied to your clipboardtype 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:
- name and slug — Category identity
- level and parentSlug / childrenSlugs — Hierarchy and relationships
- description — Category descriptive content
- metaTags — SEO metadata (title, description, keywords)
- images — Category images
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
Copied to your clipboardtype CategoryTreeView implements CategoryViewV2 {slug: String!name: String!level: IntparentSlug: StringchildrenSlugs: [String]description: StringmetaTags: CategoryMetaTagsimages: [CategoryImage]}type CategoryMetaTags {title: Stringdescription: Stringkeywords: [String]}type CategoryImage {url: String!label: Stringroles: [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
- Use the
navigationquery for storefront menus. It is heavily cached, limited to four levels, and returns only the lightweight fields needed for rendering. - Use the
categoryTreequery when you need full hierarchy metadata, descriptions, images, or SEO fields. - Use the
searchCategoryquery when the shopper or CMS workflow needs to find categories by name (for example, search-as-you-type or admin pickers), with optionalfamilyscoping and pagination. - Use the
categoriesfield on product queries only when category context (such as breadcrumbs) is needed on a product page. Omit it when it is not needed to avoid unnecessary overhead.
Navigation query depth limit
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:
- Without starting
slugs:depthsets the maximum category level included in the result for the requested scope (for example, yourfamilyfilter). Use this to discover entry points and shallow slices of the tree without naming a subtree root first. - With starting
slugs:depthis measured from each starting slug, counting that slug as level 1 of the window. Deeper descendants extend until the depth limit is reached.
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.
Navigation query examples
The navigation query signature:
Copied to your clipboardtype 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.
Copied to your clipboardquery TopLevelNavigation {navigation(family: "sports") {slugname}}
Copied to your clipboard{"data": {"navigation": [{"slug": "sports","name": "Category of sports"}]},}
The response returns a single root node with no children.
Copied to your clipboardSports
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.
Copied to your clipboardquery GetFullMenuNavigation {navigation(family: "sports") {slugnamechildren {slugnamechildren {slugnamechildren {slugname}}}}}
Copied to your clipboard{"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:
Copied to your clipboardSports└── Indoors└── Pilates└── Outdoors└── Golf
Products query with categories examples
Copied to your clipboardtype 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.
Copied to your clipboardquery {products(skus: ["shorts-red-m"]) {nameskucategories(family: "clothing") {namesluglevelparents {namesluglevel}}}}
Copied to your clipboard{"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:
Copied to your clipboardMen (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.
Copied to your clipboardquery {products(skus: ["shorts-red-m"]) {nameskucategories(family: "seasonal") {namesluglevelparents {namesluglevel}}}}
Copied to your clipboard{"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:
Copied to your clipboardtype 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.
Copied to your clipboardquery GetRootCategories {categoryTree(family: "main-catalog") {slugnamelevelparentSlugchildrenSlugs}}
Copied to your clipboard{"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.
Copied to your clipboardMen'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.
Copied to your clipboardquery GetSpecificCategorySubtree {categoryTree(family: "main-catalog"slugs: ["men/clothing", "women/clothing"]depth: 2) {slugnamelevelparentSlugchildrenSlugs}}
Copied to your clipboard{"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.
Copied to your clipboardMen'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.
Copied to your clipboardquery CategoryTree {categoryTree(slugs: ["men/clothes/shorts"], family: "clothing") {slugnamelevelparentSlugchildrenSlugsdescriptionmetaTags {titledescriptionkeywords}images {urllabelrolescustomRoles}}}
Copied to your clipboard{"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).
Copied to your clipboardtype Query {searchCategory(searchTerm: String!family: StringpageSize: IntcurrentPage: 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: "Shorts" 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.
Copied to your clipboardquery SearchCategoriesByName {searchCategory(searchTerm: "Shorts") {totalCountitems {slugnamelevel}pageInfo {currentPagepageSizetotalPages}}}
Copied to your clipboard{"data": {"searchCategory": {"totalCount": 2,"items": [{"slug": "men/clothes/shorts","name": "Shorts","level": 3},{"slug": "women/clothes/shorts","name": "Shorts","level": 3}],"pageInfo": {"currentPage": 1,"pageSize": 20,"totalPages": 1}}}}
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.
Copied to your clipboardquery SearchCategoriesInFamily {searchCategory(searchTerm: "tops"family: "main-catalog"pageSize: 10currentPage: 1) {totalCountitems {slugnameparentSlugchildrenSlugs}pageInfo {currentPagepageSizetotalPages}}}
Copied to your clipboard{"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 complete field details, 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 landing pages with SEO metadata and images | categoryTree | CategoryTreeView |
Breadcrumbs and category context on product pages | products (categories field) | CategoryProductView |
Category hierarchy management and CMS administration | categoryTree | CategoryTreeView |
Search or browse categories by name (typeahead, admin pickers) | searchCategory | CategoryTreeView (in SearchCategoryResultPage.items) |
