Skip navigation

Manage navigation menus

Hierarchies have many uses when it comes to structuring your content, such as navigation menus or related products. This overview explains 3 different ways to manage hierarchies in your Kontent projects and goes through the proper use cases for each. Each approach has its pros and cons and you should always consider the particular requirements of your project. Generally, your approach should reflect how much control you want to give to your content creators. We'll use navigation menus to illustrate the approaches.

Table of contents

    Navigation modeled in Kontent

    The most flexible method is to manage your navigation completely in Kontent using dedicated content items. These items represent menu items in your navigation and you'll define their relationships by linking them together using a linked items element.

    Navigation in Web Spotlight

    When using Web Spotlight to manage your website content, define the relationships by using a subpages element.

    This approach enables your content creators to manage navigation menus without developer assistance. Let's see how to tackle it step by step.

    1. Create a content type for navigation items

    In Content model, click Create new to create a new content type and name it Navigation item.

    Add at least these elements:

    • Text element – name it Title
    • URL slug element – name it URL
    • Linked items element for child menu items – name it Subitems
    Content type of a navigation item with Title, URL, and Subitems element.

    Define your navigation structure with a Navigation item content type.

    You can add more elements depending on your needs, such as a redirect URL.

    2. Create navigation content items

    Go to Content & assets and create a content item of the Navigation item type for each top-level item in your menu. Fill in its name, title, and URL slug accordingly. 

    If an item has child menu items, add them into the Subitems element. These items can be navigation categories lower in the hierarchy or items with actual content. 

    Then publish the navigation items together with their linked items, if they're not yet published.

    Articles navigation item with two articles and a child item with two more articles in it.

    A navigation item used to list articles hierarchy.

    3. Define the first-level navigation item

    Choose which navigation items you want to display at the top level of your menu and group them inside a root navigation item. This item doesn't need to have a URL slug and title as you need it only to know which navigation items are top-level.

    1. Create one more content item of the Navigation item type.
    2. Name it Root navigation item.
    3. Add your top-level navigation items to the Subitems element.
    Showing hierarchy created using navigation items.

    This root item holds all first-level menu items in its linked items element.

    In the following step, you will request this item via the Delivery API to generate your menu.

    4. Retrieve your navigation items

    When requesting the root navigation item via the Delivery API, specify the depth parameter to retrieve more than one level of linked items.

    • Swift
    // Tip: Find more about Swift SDK at https://docs.kontent.ai/ios import KenticoKontentDelivery let client = DeliveryClient.init(projectId: "8d20758c-d74c-4f59-ae04-ee928c0816b7") let customQuery = "items/root_navigation_item?depth=5" client.getItem(modelType: NavigationItem.self, customQuery: customQuery) { (isSuccess, deliveryItem, error) in if isSuccess { if let root = deliveryItem.item { // Use your item here } } else { if let error = error { print(error) } }
    // Tip: Find more about Swift SDK at https://docs.kontent.ai/ios import KenticoKontentDelivery let client = DeliveryClient.init(projectId: "8d20758c-d74c-4f59-ae04-ee928c0816b7") let customQuery = "items/root_navigation_item?depth=5" client.getItem(modelType: NavigationItem.self, customQuery: customQuery) { (isSuccess, deliveryItem, error) in if isSuccess { if let root = deliveryItem.item { // Use your item here } } else { if let error = error { print(error) } }
    • Java
    // Tip: Find more about Java SDK at https://docs.kontent.ai/java import kentico.kontent.delivery.*; // Initializes a DeliveryClient DeliveryClient client = new DeliveryClient("<YOUR_PROJECT_ID>"); // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models // Registers the model class for navigation items client.registerType(NavigationItem.class); // Gets navigation items and their linked items CompletionStage<NavigationItem> root = client.getItem( "root_navigation_item", NavigationItem.class, DeliveryParameterBuilder.params() .linkedItemsDepth(5) .build() ); // To use the code for Android projects, see http://docs.kontent.ai/android
    // Tip: Find more about Java SDK at https://docs.kontent.ai/java import kentico.kontent.delivery.*; // Initializes a DeliveryClient DeliveryClient client = new DeliveryClient("<YOUR_PROJECT_ID>"); // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models // Registers the model class for navigation items client.registerType(NavigationItem.class); // Gets navigation items and their linked items CompletionStage<NavigationItem> root = client.getItem( "root_navigation_item", NavigationItem.class, DeliveryParameterBuilder.params() .linkedItemsDepth(5) .build() ); // To use the code for Android projects, see http://docs.kontent.ai/android
    • JavaScript
    // Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript const KontentDelivery = require('@kentico/kontent-delivery'); const deliveryClient = new KontentDelivery.DeliveryClient({ projectId: '8d20758c-d74c-4f59-ae04-ee928c0816b7', typeResolvers: [ // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models new KontentDelivery.TypeResolver('navigation_item', (rawData) => new NavigationItem) ] }); deliveryClient.item('root_navigation_item') .depthParameter(5) .toObservable() .subscribe(response => console.log(response.item));
    // Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript const KontentDelivery = require('@kentico/kontent-delivery'); const deliveryClient = new KontentDelivery.DeliveryClient({ projectId: '8d20758c-d74c-4f59-ae04-ee928c0816b7', typeResolvers: [ // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models new KontentDelivery.TypeResolver('navigation_item', (rawData) => new NavigationItem) ] }); deliveryClient.item('root_navigation_item') .depthParameter(5) .toObservable() .subscribe(response => console.log(response.item));
    • C#
    // Tip: Find more about .NET SDKs at https://docs.kontent.ai/net using Kentico.Kontent.Delivery; // Creates an instance of the delivery client // ProTip: Use DI for this in your apps https://docs.kontent.ai/net-register-client IDeliveryClient client = DeliveryClientBuilder .WithProjectId("8d20758c-d74c-4f59-ae04-ee928c0816b7") .Build(); // Gets navigation items and their linked items // Create strongly typed models according to https://docs.kontent.ai/net-strong-types IDeliveryItemResponse<NavigationItem> response = await client.GetItemAsync<NavigationItem>("root_navigation_item", new DepthParameter(5) ); NavigationItem item = response.Item;
    // Tip: Find more about .NET SDKs at https://docs.kontent.ai/net using Kentico.Kontent.Delivery; // Creates an instance of the delivery client // ProTip: Use DI for this in your apps https://docs.kontent.ai/net-register-client IDeliveryClient client = DeliveryClientBuilder .WithProjectId("8d20758c-d74c-4f59-ae04-ee928c0816b7") .Build(); // Gets navigation items and their linked items // Create strongly typed models according to https://docs.kontent.ai/net-strong-types IDeliveryItemResponse<NavigationItem> response = await client.GetItemAsync<NavigationItem>("root_navigation_item", new DepthParameter(5) ); NavigationItem item = response.Item;
    • PHP
    // Tip: Find more about PHP SDKs at https://docs.kontent.ai/php // Defined by Composer to include required libraries require __DIR__ . '/vendor/autoload.php'; use Kentico\Kontent\Delivery\DeliveryClient; $client = new DeliveryClient('8d20758c-d74c-4f59-ae04-ee928c0816b7'); $item = $client->getItem('root_navigation_item', (new QueryParams()) ->depth(5));
    // Tip: Find more about PHP SDKs at https://docs.kontent.ai/php // Defined by Composer to include required libraries require __DIR__ . '/vendor/autoload.php'; use Kentico\Kontent\Delivery\DeliveryClient; $client = new DeliveryClient('8d20758c-d74c-4f59-ae04-ee928c0816b7'); $item = $client->getItem('root_navigation_item', (new QueryParams()) ->depth(5));
    • cURL
    curl --request GET \ --url 'https://deliver.kontent.ai/8d20758c-d74c-4f59-ae04-ee928c0816b7/items/root_navigation_item?depth=5' \ --header 'content-type: application/json'
    curl --request GET \ --url 'https://deliver.kontent.ai/8d20758c-d74c-4f59-ae04-ee928c0816b7/items/root_navigation_item?depth=5' \ --header 'content-type: application/json'
    • Ruby
    # Tip: Find more about Ruby SDKs at https://docs.kontent.ai/ruby require 'delivery-sdk-ruby' delivery_client = Kentico::Kontent::Delivery::DeliveryClient.new project_id: '8d20758c-d74c-4f59-ae04-ee928c0816b7' delivery_client.item('root_navigation_item') .depth(5) .execute do |response| item = response.item end
    # Tip: Find more about Ruby SDKs at https://docs.kontent.ai/ruby require 'delivery-sdk-ruby' delivery_client = Kentico::Kontent::Delivery::DeliveryClient.new project_id: '8d20758c-d74c-4f59-ae04-ee928c0816b7' delivery_client.item('root_navigation_item') .depth(5) .execute do |response| item = response.item end
    • TypeScript
    // Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript import { DeliveryClient, TypeResolver, ContentItem, Elements } from '@kentico/kontent-delivery'; import { NavigationItem } from './models/NavigationItem'; const deliveryClient = new DeliveryClient({ projectId: '8d20758c-d74c-4f59-ae04-ee928c0816b7', typeResolvers: [ // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models new TypeResolver('navigation_item', (rawData) => new NavigationItem) ] }); let root: NavigationItem; deliveryClient.item<NavigationItem>('root_navigation_item') .depthParameter(5) .toObservable() .subscribe(response => { root = response.item; console.log(root); });
    // Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript import { DeliveryClient, TypeResolver, ContentItem, Elements } from '@kentico/kontent-delivery'; import { NavigationItem } from './models/NavigationItem'; const deliveryClient = new DeliveryClient({ projectId: '8d20758c-d74c-4f59-ae04-ee928c0816b7', typeResolvers: [ // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models new TypeResolver('navigation_item', (rawData) => new NavigationItem) ] }); let root: NavigationItem; deliveryClient.item<NavigationItem>('root_navigation_item') .depthParameter(5) .toObservable() .subscribe(response => { root = response.item; console.log(root); });

    5. Generate a menu

    Use the retrieved data to construct a nested list of links:

    The content of the linked items is inside the modular_content property of the response.

    And that's it! You now have a dynamic menu that you can manage entirely from Kontent.

    Worked example 

    You can find a more complex example of this approach on our blog. Check out Managing Navigation Menus in KontentOpens in a new window for an in-depth guide to managing your navigation and rendering it in an MVC.NET application.

    (Optional) Create a sitemap for your website

    Similarly to generating a dynamic menu for your application, you can use the navigation modeled in Kontent to generate a sitemap for your website. Sitemaps can also be modeled using taxonomy.

    Other options

    If you don't want to model your navigation entirely in Kontent, there are other approaches that involve more or less hardcoding your navigation into your website or app.

    Hybrid navigation

    In this approach, the static part of your navigation is hard-coded and the dynamic part is generated based on the content of your project. You are not explicitly modeling your menu inside Kontent.

    This approach is suitable for projects where the basic structure of your navigation is static, but certain parts of navigation change as new content is added.

    Here's a simple example: Your app has a hardcoded menu containing an Articles menu item. The subitems of Articles are generated dynamically based on content items of the Article content type in your project.

    Let's see how this can be done.

    1. Retrieve the titles and URL slugs of your articles

    Use the projection capabilities of the Delivery API to retrieve only titles and URLs of your articles.

    • Swift
    // Tip: Find more about Swift SDK at https://docs.kontent.ai/ios import KenticoKontentDelivery let client = DeliveryClient.init(projectId:"8d20758c-d74c-4f59-ae04-ee928c0816b7") let customQuery = "items?system.type=article&elements=title,url" client.getItems(modelType: Article.self, customQuery: customQuery) { (isSuccess, itemsResponse, error) in if isSuccess { if let articles = itemsResponse?.items { // Use your items here } } else { if let error = error { print(error) } }
    // Tip: Find more about Swift SDK at https://docs.kontent.ai/ios import KenticoKontentDelivery let client = DeliveryClient.init(projectId:"8d20758c-d74c-4f59-ae04-ee928c0816b7") let customQuery = "items?system.type=article&elements=title,url" client.getItems(modelType: Article.self, customQuery: customQuery) { (isSuccess, itemsResponse, error) in if isSuccess { if let articles = itemsResponse?.items { // Use your items here } } else { if let error = error { print(error) } }
    • Java
    // Tip: Find more about Java SDK at https://docs.kontent.ai/java import kentico.kontent.delivery.*; // Initializes a DeliveryClient DeliveryClient client = new DeliveryClient("<YOUR_PROJECT_ID>"); // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models // Registers the model class for articles client.registerType(Article.class); // Gets specific elements of all articles using a simple request CompletionStage<List<Article>> articles = client.getItems( Article.class, DeliveryParameterBuilder.params() .projection("title", "url") .build() ); // To use the code for Android projects, see http://docs.kontent.ai/android
    // Tip: Find more about Java SDK at https://docs.kontent.ai/java import kentico.kontent.delivery.*; // Initializes a DeliveryClient DeliveryClient client = new DeliveryClient("<YOUR_PROJECT_ID>"); // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models // Registers the model class for articles client.registerType(Article.class); // Gets specific elements of all articles using a simple request CompletionStage<List<Article>> articles = client.getItems( Article.class, DeliveryParameterBuilder.params() .projection("title", "url") .build() ); // To use the code for Android projects, see http://docs.kontent.ai/android
    • JavaScript
    // Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript const KontentDelivery = require('@kentico/kontent-delivery'); const deliveryClient = new KontentDelivery.DeliveryClient({ projectId: '8d20758c-d74c-4f59-ae04-ee928c0816b7', typeResolvers: [ // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models new KontentDelivery.TypeResolver('article', (rawData) => new Article()) ] }); deliveryClient.items() .type('article') .elementsParameter(['title', 'url']) .toObservable() .subscribe(response => console.log(response));
    // Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript const KontentDelivery = require('@kentico/kontent-delivery'); const deliveryClient = new KontentDelivery.DeliveryClient({ projectId: '8d20758c-d74c-4f59-ae04-ee928c0816b7', typeResolvers: [ // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models new KontentDelivery.TypeResolver('article', (rawData) => new Article()) ] }); deliveryClient.items() .type('article') .elementsParameter(['title', 'url']) .toObservable() .subscribe(response => console.log(response));
    • C#
    // Tip: Find more about .NET SDKs at https://docs.kontent.ai/net using Kentico.Kontent.Delivery; // Creates an instance of the delivery client // ProTip: Use DI for this in your apps https://docs.kontent.ai/net-register-client IDeliveryClient client = DeliveryClientBuilder .WithProjectId("8d20758c-d74c-4f59-ae04-ee928c0816b7") .Build(); // Gets specific elements of all articles // Create strongly typed models according to https://docs.kontent.ai/net-strong-types IDeliveryItemListingResponse<Article> response = await client.GetItemsAsync<Article>( new EqualsFilter("system.type", "article"), new ElementsParameter("title", "url") ); var items = response.Items;
    // Tip: Find more about .NET SDKs at https://docs.kontent.ai/net using Kentico.Kontent.Delivery; // Creates an instance of the delivery client // ProTip: Use DI for this in your apps https://docs.kontent.ai/net-register-client IDeliveryClient client = DeliveryClientBuilder .WithProjectId("8d20758c-d74c-4f59-ae04-ee928c0816b7") .Build(); // Gets specific elements of all articles // Create strongly typed models according to https://docs.kontent.ai/net-strong-types IDeliveryItemListingResponse<Article> response = await client.GetItemsAsync<Article>( new EqualsFilter("system.type", "article"), new ElementsParameter("title", "url") ); var items = response.Items;
    • PHP
    // Tip: Find more about PHP SDKs at https://docs.kontent.ai/php // Defined by Composer to include required libraries require __DIR__ . '/vendor/autoload.php'; use Kentico\Kontent\Delivery\DeliveryClient; use Kentico\Kontent\Delivery\QueryParams; $client = new DeliveryClient('8d20758c-d74c-4f59-ae04-ee928c0816b7'); $items = $client->getItems((new QueryParams()) ->equals('system.type', 'article') ->elements(array("title", "url")));
    // Tip: Find more about PHP SDKs at https://docs.kontent.ai/php // Defined by Composer to include required libraries require __DIR__ . '/vendor/autoload.php'; use Kentico\Kontent\Delivery\DeliveryClient; use Kentico\Kontent\Delivery\QueryParams; $client = new DeliveryClient('8d20758c-d74c-4f59-ae04-ee928c0816b7'); $items = $client->getItems((new QueryParams()) ->equals('system.type', 'article') ->elements(array("title", "url")));
    • cURL
    curl --request GET \ --url 'https://deliver.kontent.ai/8d20758c-d74c-4f59-ae04-ee928c0816b7/items?system.type=article&elements=title%2Curl' \ --header 'content-type: application/json'
    curl --request GET \ --url 'https://deliver.kontent.ai/8d20758c-d74c-4f59-ae04-ee928c0816b7/items?system.type=article&elements=title%2Curl' \ --header 'content-type: application/json'
    • Ruby
    # Tip: Find more about Ruby SDKs at https://docs.kontent.ai/ruby require 'delivery-sdk-ruby' delivery_client = Kentico::Kontent::Delivery::DeliveryClient.new project_id: '8d20758c-d74c-4f59-ae04-ee928c0816b7' delivery_client.items('system.type'.eq('article')) .elements(%w[title url]) .execute do |response| items = response.items end
    # Tip: Find more about Ruby SDKs at https://docs.kontent.ai/ruby require 'delivery-sdk-ruby' delivery_client = Kentico::Kontent::Delivery::DeliveryClient.new project_id: '8d20758c-d74c-4f59-ae04-ee928c0816b7' delivery_client.items('system.type'.eq('article')) .elements(%w[title url]) .execute do |response| items = response.items end
    • TypeScript
    // Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript import { DeliveryClient, TypeResolver } from '@kentico/kontent-delivery'; import { Article } from './models/Article'; const deliveryClient = new DeliveryClient({ projectId: '8d20758c-d74c-4f59-ae04-ee928c0816b7', typeResolvers: [ // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models new TypeResolver('article', (rawData) => new Article) ] }); let articles: Article[]; deliveryClient.items<Article>() .type('article') .elementsParameter(['title', 'url']) .toObservable() .subscribe(response => { articles = response.items; console.log(articles); });
    // Tip: Find more about JS/TS SDKs at https://docs.kontent.ai/javascript import { DeliveryClient, TypeResolver } from '@kentico/kontent-delivery'; import { Article } from './models/Article'; const deliveryClient = new DeliveryClient({ projectId: '8d20758c-d74c-4f59-ae04-ee928c0816b7', typeResolvers: [ // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models new TypeResolver('article', (rawData) => new Article) ] }); let articles: Article[]; deliveryClient.items<Article>() .type('article') .elementsParameter(['title', 'url']) .toObservable() .subscribe(response => { articles = response.items; console.log(articles); });

    For taxonomy-driven navigation, you can retrieve items tagged with a specific taxonomy term.

    As your content creators write and publish more articles, your menu is updated automatically. They can't and don't have to change the navigation manually, which means less work and less potential for mistakes. However, structural changes have to be implemented by developers.

    Fully hardcoded navigation

    In this method, no part of your navigation is modeled in Kontent, it's all in the code. See our sample application as an example.

    A screenshot of the top navigation in our sample app.

    The Dancing Goat sample app with hard-coded navigation.

    This approach is suitable for small projects with static navigation where you don't want your content creators to make changes to the navigation. It's fast and simple at the beginning, but every subsequent change must be implemented by a developer.

    Summary

    In this tutorial, you have learned about three different ways to manage navigation in your Kontent projects. You can pick the one that best suits your priorities, such as the flexibility and empowerment of your content creators (navigation fully modeled in Kontent), speed of initial development (hardcoded navigation), or low maintenance cost (hybrid navigation).

    Each approach has a trade-off but you can also combine them together and manage each part of your navigation separately to achieve the most efficient way.

    What's next?