---
title: Extend the CMS navigation
slug: cms-11/dev-docs/extending-the-navigation
docTags: 
createdAt: 2026-07-22T19:45:16.749Z
---

:::hint{type="info"}
**CMS version 11.21 and higher.**
The following section shows how to extend the navigation for Optimizely Content Management System (CMS) version 11.21 and higher.
:::

You can extend the navigation to let editors access CMS and other products from the global menu.

- The `[MenuItem]` attribute adds an interface to the menu. See [Add and configure menu items](docId\:V38aTqg5RzUONfyYmSjd8).
- You can also configure and implement a custom menu provider; see [Use menu providers](docId\:NlBW1JGWpGWT6TK7523fa).

Attributes and menu providers must reside in an assembly configured as [a shell module](docId\:FYFeCBL_bBWB7Ehw5M2qu).

CMS introduced a global navigation menu in version 11.21 of CMS UI that differs slightly from how it worked before. If you upgrade the global navigation menu, see [Upgrade navigation](docId\:BfIhQupF4VB2iokTh_5n4).

:::hint{type="info"}
**Optimizely Content Management System (CMS) version 10 to 11.20.**
The following sections show how to extend the navigation for Optimizely Content Management System (CMS) version 10 to 11.20.
:::

You can extend the navigation to let editors access CMS and other products from the global menu.

- The `[MenuItem]` attribute adds an interface to the menu.
- You can also configure and implement a custom menu provider.

Attributes and menu providers must reside in an assembly configured as a shell module.

## \[MenuItem] attribute

Use MenuItem on ASP.NET MVC controllers. `MenuItem` adds links to the top menu (and requires the module to be registered; see [Configure Shell modules](docId\:DNal_JrQ2rB1HrWlcgbGo). The required `menuPath` parameter is a logical path of the menu element in the menu. Use the `Url` parameter to add a linked URL to the interface.

The following example adds a menu item to the top menu bar with the text **Start**, and the ASP.NET MVC route table infers the URL:

```csharp
public class DashboardController: Controller {
  [MenuItem("/global/dashboard", Text = "Start")]
  public ActionResult Index() {}
}
```

When you add menu items, implement the page to include CMS's shell styling and render the menu so you can navigate back.

The key functions to render a menu are:

- `@Html.Raw(ClientResources.RenderResources("Navigation"))`
- `@Html.Raw(Html.ShellInitializationScript()) `
- `@Html.Raw(Html.GlobalMenu()) `

In the following code example, the `GlobalMenu` line renders the HTML markup needed for the menu. Scripts and styles come from the `RenderResources` in the `<head>` tag.

```csharp
@using EPiServer.Framework.Web.Resources
@using EPiServer.Shell.Navigation
    
<!DOCTYPE html>
<html>
  <head>
    <title>@ViewBag.Title</title>
    
    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
    
    <!-- Shell -->
    @Html.Raw(ClientResources.RenderResources("ShellCore"))
    @Html.Raw(ClientResources.RenderResources("ShellWidgets"))
    
    <!-- LightTheme -->
    @Html.Raw(ClientResources.RenderResources( "ShellCoreLightTheme"))
     
    <!-- Navigation -->
    @Html.Raw(ClientResources.RenderResources("Navigation"))
    
    <!-- Dojo Dashboard -->
    @Html.Raw(ClientResources.RenderResources( "DojoDashboardCompatibility", new[] { ClientResourceType.Style }))
    
  </head>
  <body>
    @Html.Raw(Html.ShellInitializationScript())
    @Html.Raw(Html.GlobalMenu())
    <div>
      @RenderBody()
    </div>
  </body>
</html>
```

### Localize menu items with the MenuItem attribute

You can localize menu items defined with attributes with a static property of a resource class. `ResourceType` references a class with a static property. `TextResourceKey` is the name of the static property that returns the localized text.

Example:

```csharp
[MenuItem("/global/dashboard", TextResourceKey = "Start", ResourceType = typeof(MenuResources))]
```

### Organize menu items

The menu path organizes menu items in a tree structure. Menu items in the top menu are in the `/global` bucket. The next segment is the name of the menu section, for example, `/global/cms`. The last segment represents the actual user interface, for example, `/global/cms/edit`.

### Create a link in a menu

Some menu items have a URL that creates a link in the menu. The page at the end of this URL should render a menu where the corresponding menu path is selected.

### Permissions with the MenuItem attribute

You can restrict who sees a certain menu item by using the `[Authorize]` attribute. For example:

```csharp
public class DashboardController: Controller {
  [MenuItem("/global/dashboard")]
  [Authorize(Roles = "NoOne")]
  public ActionResult Index() {}
}
```

## Use menu providers

The building blocks of the navigation are menu providers and menu items. A menu provider enumerates menu items organized in a tree according to their menu path. CMS contains a menu provider that looks at `[MenuItem]` attributes and provides them as menu items.

### IMenuProvider

You can extend the standard menu by implementing a menu provider as an attribute alternative. The menu provider returns menu items that are correctly localized. To use a menu provider, implement the `IMenuProvider` interface, decorate it with the `[MenuProvider]` attribute, and make it part of a registered shell module.

### Add menu items

You can add menu sections and sub-menu items such as menu sections, drop-downs, URLs, and popup menu items. Each menu item defines a path, determining its location in the menu hierarchy. For example, a URL menu item with path `/global/cms/myMenu` is placed in the CMS section of the menu (which has the path `/global/cms`).

Types:

- **URL Menu Item** are links; a click navigates to a specified URL.
- **Popup Menu Item** are links opened in a window (JavaScript `window.open`).
- **Section Menu Item** are top menu sections that open an underlying menu.
- **Drop-Down Menu Item** are drop-down-style menu items designed for the utility menu area (right).

Example:

```csharp
/// <summary>
/// Provides menu items for the CMS product.
/// </summary>
[MenuProvider]
public class CmsMenuProvider: IMenuProvider {
  /// <summary>
  /// Provides the CMS menu section and the CMS settings section.
  /// </summary>
  /// <returns>
  /// A list of <see cref="MenuItem"/>s that the provider exposes.
  /// </returns>
  public IEnumerable < MenuItem > GetMenuItems() {
    // Create the top menu section
    var section = new SectionMenuItem("CMS", // Title
      "/global/cms"); // Logical path

    // Visible to CMS editors
    section.IsAvailable = (request) =>
      PrincipalInfo.HasEditAccess;

    // Create the edit menu item below the top section
    var cmsEdit = new UrlMenuItem("Edit", // Title
      "/global/cms/edit", // Logical path
      "/path/to/edit/default.aspx"); // URL

    // Visible to CMS editors
    cmsEdit.IsAvailable = (request) =>
      PrincipalInfo.HasEditAccess;

    return new MenuItem[] {
      section,
      cmsEdit
    };
  }
}
```

### Localize menu items with a menu provider

A menu provider returns localized menu items.

### Permissions with the menu provider

The menu provider can defer permission filtering to the menu item by setting the `IsAvailable` delegate to a method that checks access for the user provided with the `RequestContext` parameter.

### Flow of menu items

Menu items flow from the providers into a hierarchical model rendered into HTML.

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/A83IXUVUgwOKETa25ehYf-20260722-194640.png "extending_navigation.png")

### Configure navigation in web.config

To extend the menu, you can, for example, configure `web.config` as follows:

```xml
<episerver.shell>
  <navigation>
    <add text="Intranet" menuPath="/global/intra" 
         url="http://intra" sortIndex="100" />
    <add text="My section" menuPath="/global/my" 
         menuItemType="Section" sortIndex="300" />
    <add text="Edit" menuPath="/global/my/edit" 
         url="/my/edit.aspx" sortIndex="100" />
    <add text="Admin" menuPath="/global/my/admin" 
         url="/my/admin.aspx" sortIndex="200" />
  </navigation>
</episerver.shell>
```

## Examples

### Global menu button

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/pAx6Hho6enJDAIktdkE_N-20260722-194640.png "examples-01-global-button.png")

```csharp
using System.Web.Mvc;
using EPiServer.Shell.Navigation;

namespace Alloy.Business {
  public class EpiServerWorldGlobalMenuItem: Controller {
    [MenuItem(MenuPaths.Global + "/globalLink", SortIndex = SortIndex.First - 10, Text = "Episerver world",
      Url = "https://world.episerver.com/cms/")]
    public ActionResult Index(string viewName) {
      return View();
    }
  }
}
```

### Extend the CMS menu section

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/_GmAnJuU8mMRnIYaiP0uD-20260722-194640.png "examples-02-extending-cms-section.png")

```csharp
using System.Collections.Generic;
using EPiServer;
using EPiServer.Security;
using EPiServer.Shell.Navigation;

namespace Alloy.Business {
  [MenuProvider]
  public class CmsMenuProvider: IMenuProvider {
    public IEnumerable < MenuItem > GetMenuItems() {
      var menuItems = new List < MenuItem > ();
      menuItems.Add(new UrlMenuItem("Another link to Admin",
        MenuPaths.Global + "/cms" + "/cmsMenuItem",
        UriSupport.ResolveUrlFromUIAsRelativeOrAbsolute("Admin/Default.aspx")) {
        SortIndex = SortIndex.First + 25,
          IsAvailable = (request) => PrincipalInfo.HasAdminAccess
      });

      return menuItems;
    }
  }
}
```

### Global menu section

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/PP84MQTvrK8xP9v7147IO-20260722-194640.png "examples-03-new-global-section.png")

:::hint{type="info"}
**Note**
While the example below works, you should not use the main navigation for external links.
:::

```csharp
using System.Collections.Generic;
using EPiServer.Security;
using EPiServer.Shell.Navigation;

namespace Alloy.Business {
  [MenuProvider]
  public class GlbalSectionMenuProvider: IMenuProvider {
    const string MainMenuPath = MenuPaths.Global + "/customSection";

    public IEnumerable < MenuItem > GetMenuItems() {
      var menuItems = new List < MenuItem > ();

      menuItems.Add(new SectionMenuItem("Episerver Forum", MainMenuPath) {
        SortIndex = SortIndex.Last + 10,
          IsAvailable = (request) => PrincipalInfo.HasEditAccess
      });

      menuItems.Add(new UrlMenuItem("CMS", MainMenuPath + "/item1",
        "https://world.episerver.com/forum/developer-forum/-Episerver-75-CMS/") {
        SortIndex = 1,
      });

      menuItems.Add(new UrlMenuItem("Commerce", MainMenuPath + "/item2",
        "https://world.episerver.com/forum/developer-forum/Episerver-Commerce/") {
        SortIndex = 2,
      });

      menuItems.Add(new UrlMenuItem("Forms", MainMenuPath + "/item3",
        "https://world.episerver.com/forum/developer-forum/episerver-forms/") {
        SortIndex = 3,
      });

      return menuItems;
    }
  }
}
```

### Extend the User Settings menu using controller action

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/F67mktTP-S_l97nX_bdOh-20260722-194640.png "examples-04-extending-menu-using-controller.png")

```csharp
using System.Web.Mvc;
using EPiServer.Shell.Navigation;

namespace Alloy.Business {
  /// <summary>
  ///  Menu item under User Settings menu (menu added using controller action)
  /// </summary>
  /// <returns></returns>
  public class EpiServerWorldMenuItem: Controller {
    [MenuItem(MenuPaths.UserSettings, SortIndex = SortIndex.First - 10, Text = "About Episerver",
      Url = "https://www.episerver.com/about/company/overview/")]
    public ActionResult Index(string viewName) {
      return View();
    }
  }
}
```

### Extend the User Settings menu using the menu provider

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/rceZT1yJrVel_gmd7f8TT-20260722-194640.png "examples-05-extending-menu-using-provider.png")

```csharp
using System.Collections.Generic;
using EPiServer.Shell.Navigation;

namespace Alloy.Business {
  [MenuProvider]
  public class UserSettingsMenuProvider: IMenuProvider {
    public IEnumerable < MenuItem > GetMenuItems() {
      var menuItems = new List < MenuItem > ();
      var item = new UrlMenuItem("Documentation", MenuPaths.User + "/episerver2",
        "https://world.episerver.com/documentation/") {
        SortIndex = SortIndex.Last + 10
      };
      menuItems.Add(item);
      return menuItems;
    }
  }
}
```

### Drop-down menu with items

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/RvLIEIqDppoUqBp0o3Tot-20260722-194640.png "examples-06-extending-menu-using-provider.png")

```csharp
using System.Collections.Generic;
using EPiServer.Shell.Navigation;

namespace Alloy.Business {
  [MenuProvider]
  public class DropdownMenuProvider: IMenuProvider {
    const string DropdownMenuPath = MenuPaths.Global + "/customDropdownMenu";

    public IEnumerable < MenuItem > GetMenuItems() {
      var menuItems = new List < MenuItem > ();

      var userMenu = new DropDownMenuItem("Episerver blogs", DropdownMenuPath) {
        SortIndex = SortIndex.Last - 20,
          Alignment = MenuItemAlignment.Right
      };
      menuItems.Add(userMenu);

      menuItems.Add(new UrlMenuItem("CMS", DropdownMenuPath + "/item1",
        "https://world.episerver.com/blogs/?type=cmsblog&page=1") {
        SortIndex = 1,
      });

      menuItems.Add(new UrlMenuItem("Commerce", DropdownMenuPath + "/item2",
        "https://world.episerver.com/blogs/?type=commerceblog&page=1") {
        SortIndex = 2,
      });

      menuItems.Add(new UrlMenuItem("Find", DropdownMenuPath + "/item3",
        "https://world.episerver.com/blogs/?type=findblog&page=1") {
        SortIndex = 3,
      });

      return menuItems;
    }
  }
}
```
