Control Panel Sections

Modules and plugins can add new sections to the control panel using the EVENT_REGISTER_CP_NAV_ITEMS (opens new window) event:

use craft\events\RegisterCpNavItemsEvent;
use craft\web\twig\variables\Cp;
use yii\base\Event;

public function init()

        function(RegisterCpNavItemsEvent $event) {
            $event->navItems[] = [
                'url' => 'section-url',
                'label' => 'Section Label',
                'icon' => '@mynamespace/path/to/icon.svg',

    // ...

Each item within the navItems (opens new window) array can have the following keys:

  • url – The URL that the nav item should link to. (It will be run through craft\helpers\UrlHelper::cpUrl() (opens new window).)
  • label – The user-facing nav item label.
  • icon – The path to the icon SVG that should be used. (It can begin with an alias.)
  • badgeCount (optional) – The badge count that should be displayed in the nav item.
  • subnav (optional) – An array of subnav items that should be visible when your section is accessed. (See Subnavs.)

For Craft to properly designate an item as “active,” its url must be registered with a relative path to the plugin or module’s control panel section. Any subnav paths should begin with url in order to appear selected when active.

If your section has a sub-navigation, each subnav item within your subnav array should be represented by a sub-array with url and label keys:

'subnav' => [
    'foo' => ['label' => 'Foo', 'url' => 'section-url/foo'],
    'bar' => ['label' => 'Bar', 'url' => 'section-url/bar'],

    // Display a subnav badge count by adding the optional `badgeCount` key:
    'baz' => ['label' => 'Baz', 'url' => 'section-url/baz', 'badgeCount' => 5],

Your templates can specify which subnav item should be selected by setting a selectedSubnavItem variable to the key of the nav item:

{% set selectedSubnavItem = 'bar' %}

# Plugin Sections

Plugins that only need to add one section can set the $hasCpSection property on their primary plugin class, rather than using the EVENT_REGISTER_CP_NAV_ITEMS (opens new window) event:


namespace mynamespace;

class Plugin extends \craft\base\Plugin
    public bool $hasCpSection = true;

    // ...

You can alternatively set a hasCpSection value in composer.json as noted in the plugin guide. We don’t recommend setting it in both places, however, since the value set in composer.json will override your public class property’s value and likely create confusion.

You can modify aspects of the plugin’s control panel nav item by overriding its getCpNavItem() (opens new window) method:

public function getCpNavItem(): ?array
    $item = parent::getCpNavItem();
    $item['badgeCount'] = 5;
    $item['subnav'] = [
        'foo' => ['label' => 'Foo', 'url' => 'plugin-handle/foo'],
        'bar' => ['label' => 'Bar', 'url' => 'plugin-handle/bar'],
        'baz' => ['label' => 'Baz', 'url' => 'plugin-handle/baz'],
    return $item;

If you do this, Craft will automatically add a new user permission for your plugin, and only show the nav item for users that have it.

Clicking on a plugin’s section will take the user to /admin/plugin-handle, which will attempt to load an index.html or index.twig template within the plugin’s template root (its templates/ folder within its base source folder).

See Control Panel Templates for more information about developing control panel templates.

Alternatively, you can route /admin/plugin-handle requests to a controller action (or a different template) by registering a control panel route from your plugin’s init() method:

use craft\events\RegisterUrlRulesEvent;
use craft\web\UrlManager;
use yii\base\Event;

public function init()
        function(RegisterUrlRulesEvent $event) {
            $event->rules['plugin-handle'] = 'plugin-handle/foo/bar';

These rules use the same format as those defined in routes.php. Find additional examples of control panel and site routes in the routing section of the controllers documentation.