Categories


Archives


Recent Posts


Categories


Magento 2: Admin Menu Items

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

In the first article of this series, we described how to create a simple MVC/MVVM endpoint in Magento 2. Implicit, but unstated, was that we were setting up an endpoint for Magento’s frontend cart application. While the backend admin application uses the same MVC/MVVM/View system as the frontend, there’s additional features based on backend security and user interface conventions in Magento 2.

Today we’re going to start exploring the Menu Item system. Our end goal is to add a link to Magento’s left side admin application which, as you’ll learn, is the first step towards adding a backend page to Magento 2.

Adding a Menu to the Left Side

Menu Items are the links along the left side of the Magento admin console application. For example — if you navigate to

Content -> Elements -> Pages

the Pages hyperlink is a Menu Item. If you right click on this hyperlink and select Copy Link Address, you’ll see something like this

http://magento.example.xom/admin/cms/page/index/key/ed2ddfe814ba40acb42b6fd4e95be717d32528860c3960d5e178b50e3691e0b0/

There’s a few things we’ll want to make note of with regards to the structure of an admin URL. First, admin URLs have a four segment structure.

admin/cms/page/index

The first segment, admin, is the area. All URLs in the Magento admin start with this additional URL segment. The next three segments are the URL front name, controller name, and action name.

Front Name:      cms
Controller Name: page
Action Name:     index

Same as a standard frontend controller, Magento 2 combines all three of these segments to create a single controller class name. Magento 2 is a “One URL, One Controller” system. If you’re not sure what that means, don’t worry, future examples below will clear that up.

Finally, all Menu Items have an additional URL parameter named key, with a corresponding value

key/ed2ddfe814ba40acb42b6fd4e95be717d32528860c3960d5e178b50e3691e0b0/

This special code is required for all URLs, and is here to help prevent cross site script attacks. If you fail to include this key with your URL, Magento will reject the request as invalid.

This URL key is why we need to create a Menu Item in the first place — without Magento generating a URL key for us, there’s no simple safe way to access a standard admin controller.

Creating a Menu Item

We’re going to dive right in and create a new Menu Item. First, we’ll need to create a Magento module to hold our Menu Item. You can create the base module files using the pestle command line framework’s generate_module command.

$ pestle.phar generate_module Pulsestorm MenuTutorial 0.0.1

and then enable the module in Magento by running the following two commands

$ php bin/magento module:enable Pulsestorm_MenuTutorial
$ php bin/magento setup:upgrade    

If you’re interested in creating a module by hand, or curious what the above pestle command is actually doing, take a look at our Introduction to Magento 2 — No More MVC article.

Next, we’ll want to add a menu.xml file to our module. Create the following file with the following contents.

<!-- File: app/code/Pulsestorm/MenuTutorial/etc/adminhtml/menu.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
    <menu>
        <add id="Pulsestorm_MenuTutorial::top_level_example"
             title="Top Level Example"
             module="Pulsestorm_MenuTutorial"
             sortOrder="9999"
             resource="Magento_Backend::content"
            />
    </menu>
</config>

With the above in place, clear your Magento cache and load a backend page. You should see a new, top level Menu Item at the bottom of the admin navigation

Congratulations! You’ve just created your first Menu Item.

At its simplest, a menu.xml file is a collection of <add/> nodes. Each of these nodes adds a Menu Item to Magento’s backend.

The id attribute defines a unique identifier for this node. By convention, (but not required) this should the the name of the module (Pulsestorm_MenuTutorial), followed by two colons (::), followed by lowercase text that describes what the module does (top_level_example).

The title attribute (Top Level Example) controls the text an end users sees for the Menu Item.

The module attribute should match the current module — this may seem redundant with the id attribute, but remember, it’s only convention that forces you to use the module name in a Menu Item’s id.

The sortOrder attribute controls how this Menu Item is sorted with the other Menu Items in the system. The 9999 value ensures our module shows up under all the stock Menu Items.

Finally, the resource attribute defines the ACL rule a user must have in order to access this Menu Item. Normally, you’d define your own ACL rule in the same module, and use it here. For simplicity’s sake, we’re using a predefined ACL rule (Magento_Backend::content) from a standard Magento module. If the logged in user does not have access to the configured ACL rule, the Menu Item will not render for them.

For Magento 1 developers, its worth noting that the ACL rule requirements for a module have been somewhat simplified. All we need to do in Magento 2 is specify an id — there’s no confusing matching of ACL hierarchies with Menu Item hierarchies. The tradeoff, of course, is it’s no longer possible to infer a Menu Item’s ACL rule based on its hierarchy.

Menu Items Hyperlinks

You may have noticed our top level Menu Item doesn’t actually link to anything. While its possible for a top level Menu Item to be a hyperlink, by convention the left side navigation items are used for organization. i.e., they contain child links. Let’s try adding a new Menu Item that actually has a hyperlink. Change your menu.xml so it matches the following

<!-- File: app/code/Pulsestorm/MenuTutorial/etc/adminhtml/menu.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
    <menu>
        <add id="Pulsestorm_MenuTutorial::top_level_example"
             title="Top Level Example"
             module="Pulsestorm_MenuTutorial"
             sortOrder="9999"
             resource="Magento_Backend::content"
            />
        <!-- START: new node -->                
        <add id="Pulsestorm_MenuTutorial::second_level_example"
             title="Second Level Example"
             module="Pulsestorm_MenuTutorial"
             sortOrder="9999"
             resource="Magento_Backend::content"

             parent="Pulsestorm_MenuTutorial::top_level_example"
             action="cms/page/index"
            />                            
        <!-- END:   new node -->                
    </menu>
</config>

Here we’ve add a new <add/> node to our menu.xml file. We’ve given this new Menu Item a new, unique id attribute and a new title. Normally, this Menu Item would have a different resource from its parent (giving systems owners fine grained control over which modules show up) but to keep things simple we’re using the same rule as our parent item (Magento_Backend::content).

The important parts are our parent and action attributes. The parent attribute should be a Menu Item ID that already exists, and will tell Magento this new Menu Item (Pulsestorm_MenuTutorial::second_level_example) is a child of the parent. Magento 1 developers will want to take note — these add nodes all exist at the same XML tree level — the parent/child relationships between them are dictated by these parent attributes.

The action attribute is the three segment, frontName/controllerName/actionName Magento 2 URL identifier. We’ve borrowed an action from the CMS module for this tutorial. Clear your cache, reload the page, and your Top Level Example Menu Item should now trigger a fly-out menu that contains your single Second Level Example link.

If you click on this link, your browser will being you to the specified action.

If you’ve used the Magento admin, you’ve probably notices most core modules have Menu Items organized under sub-menus. You can do that with your own modules! Add the following third node to your menu.xml file

<!-- File: app/code/Pulsestorm/MenuTutorial/etc/adminhtml/menu.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
    <menu>
        <!-- ... -->
        <add id="Pulsestorm_MenuTutorial::third_level_example"
             title="Third Level Example"
             module="Pulsestorm_MenuTutorial"
             sortOrder="9999"
             resource="Magento_Backend::content"

             parent="Pulsestorm_MenuTutorial::second_level_example"
             action="cms/page/index"
            />                            

    </menu>
</config>

Here we’ve created a third level node, and set its parent to our second level node. If you clear your cache and reload the page with the above menu in place, your fly-out menu should look like this

Notice that our second level Menu Item has become a hyperlink-less parent header, with the third level item as a link. Magento Menu Items are limited to three levels of hierarchy — you can’t go any deeper than this.

Choosing a Parent Menu

In the above examples, we showed you how to create your own top level Menu items. Before you do this in your own modules, its a good idea to ask yourself if your module really needs a new top level item. Using the Menu Item system, its possible to add your Menu Items to existing Magento core menu categories, and doing so avoids jamming up the admin with too many top level modules.

For example, if you change the following Menu Item

<!-- File: app/code/Pulsestorm/MenuTutorial/etc/adminhtml/menu.xml -->
<add id="Pulsestorm_MenuTutorial::third_level_example"
     title="Third Level Example"
     module="Pulsestorm_MenuTutorial"
     sortOrder="9999"
     resource="Magento_Backend::content"

     parent="Pulsestorm_MenuTutorial::second_level_example"
     action="cms/page/index"
    />                            

such that its parent points to a core Magento Menu Item instead

<!-- File: app/code/Pulsestorm/MenuTutorial/etc/adminhtml/menu.xml -->
<add id="Pulsestorm_MenuTutorial::third_level_example"
     title="Third Level Example"
     module="Pulsestorm_MenuTutorial"
     sortOrder="9999"
     resource="Magento_Backend::content"

     parent="Magento_Backend::system"
     action="cms/page/index"
    />         

Your menu will appear under the top level System Menu Item instead. There’s no clear guidance on where to put your module’s Menu Items — this is something every module developer will need to decide on their own.

If you need helping finding the id values of Magento core Menu Items and are familiar with the unix command line, the following command pipeline may be of interest

$ find vendor/magento/ -name menu.xml | xargs grep 'title="System"'

Putting it Together with Pestle

We ran through creating a Menu Item manually as a teaching exercise. Fortunately, pestle has a generate_menu command that makes creating Menu Items simple. Here’s how add a new Menu Item to Magento’s own System -> Other Settings menu with pestle.

First, lets use pestle to create a proper ACL rule for our Menu Item with the following.

$ pestle.phar generate_acl
Which Module? (Pulsestorm_HelloWorld)] Pulsestorm_MenuTutorial
Rule IDs? (Pulsestorm_MenuTutorial::top,Pulsestorm_MenuTutorial::config,)] Pulsestorm_MenuTutorial::menu_items,Pulsestorm_MenuTutorial::example_1
Created /path/to/magento2/app/code/Pulsestorm/MenuTutorial/etc/acl.xml

and then edit the generated acl.xml to give our rules some titles.

<!-- File: app/code/Pulsestorm/MenuTutorial/etc/acl.xml -->
<resource id="Pulsestorm_MenuTutorial::menu_items" title="Tutorial Menu Items">
    <resource id="Pulsestorm_MenuTutorial::example_1" title="First Example"/>
</resource>

If you’re not sure what the above does, checkout the Understanding Access Control List Rules article.

Next, we’ll use the generate_menu command. Even if you’re familiar with pestle, you may want to read through our description of the interactive command line workflow below, as there’s a new pestle feature in play.

$ pestle.phar generate_menu
Module Name? (Pulsestorm_HelloGenerate)] Pulsestorm_MenuTutorial
Is this a new top level menu? (Y/N) (N)] N
Select Parent Menu: 
[1] System  (Magento_Backend::system)
[2] Dashboard   (Magento_Backend::dashboard)
[3] System  (Magento_Backend::system)
[4] Marketing   (Magento_Backend::marketing)
[5] Content (Magento_Backend::content)
[6] Stores  (Magento_Backend::stores)
[7] Products    (Magento_Catalog::catalog)
[8]     (Magento_Backend::system_currency)
[9] Customers   (Magento_Customer::customer)
[10] Find Partners & Extensions (Magento_Marketplace::partners)
[11] Reports    (Magento_Reports::report)
[12] Sales  (Magento_Sales::sales)
[13] UMC    (Umc_Base::umc)
 ()] 1
Use [Magento_Backend::system] as parent? (Y/N) (N)] N
Select Parent Menu: 
[1] Report  (Magento_Backend::system_report)
[2] Tools   (Magento_Backend::system_tools)
[3] Data Transfer   (Magento_Backend::system_convert)
[4] Other Settings  (Magento_Backend::system_other_settings)
[5] Extensions  (Magento_Integration::system_extensions)
[6] Permissions (Magento_User::system_acl)
 ()] 4
Menu Link ID (Pulsestorm_MenuTutorial::unique_identifier)] Pulsestorm_MenuTutorial::a_menu_item
ACL Resource (Pulsestorm_MenuTutorial::a_menu_item)] Pulsestorm_MenuTutorial::example_1
Link Title (My Link Title)] Look at me, I'm a link
Three Segment Action (frontname/index/index)] pulsestorm_menututorial/index/index
Sort Order? (10)] 9999
Writing: /path/to/magento2/app/code/Pulsestorm/MenuTutorial/etc/adminhtml/menu.xml
Done.

The first question,

Module Name? (Pulsestorm_HelloGenerate)] Pulsestorm_MenuTutorial

determines which module pestle will create a menu.xml file in. The next set of questions

Is this a new top level menu? (Y/N) (N)] N
Select Parent Menu: 
[1] System  (Magento_Backend::system)
//...
[13] UMC    (Umc_Base::umc)
 ()] 1
Use [Magento_Backend::system] as parent? (Y/N) (N)] N
Select Parent Menu: 
[1] Report  (Magento_Backend::system_report)
//...
[6] Permissions (Magento_User::system_acl)
 ()] 4

are an interactive workflow that let you create a new menu identifier, or add your menu to an existing menu. i.e. “Is this a parentless item” and/or “what is this Menu’s parent”. Despite asking multiple questions, this is a single pestle argument (see below, and also the callback argument documentation)

The next question

Menu Link ID (Pulsestorm_MenuTutorial::unique_identifier)] Pulsestorm_MenuTutorial::a_menu_item

is where you enter the id attribute to use in menu.xml. Then there’s

ACL Resource (Pulsestorm_MenuTutorial::a_menu_item)] Pulsestorm_MenuTutorial::example_1

which is where you add the value used in the resource attribute. The last two questions

Three Segment Action (frontname/index/index)] pulsestorm_menututorial/index/index
Sort Order? (10)] 9999

allow you to set the action and sortOrder attributes. With the above in place, reload your page, and your Menu Item can be found under System -> Other Settings. If you’re interested in using a pestle one liner, that would be

$ pestle.phar generate_menu Pulsestorm_MenuTutorial Magento_Backend::system_other_settings Pulsestorm_MenuTutorial::a_menu_item Pulsestorm_MenuTutorial::example_1 "Look at me, I'm a link" pulsestorm_menututorial/index/index 9999

Notice how the entire interactive workflow to select a Menu Item ID is represented by one argument (Pulsestorm_MenuTutorial::a_menu_item).

Wrap Up

That the basics of Menu Item creation in Magento 2. If you followed along with all the code examples, you may have noticed our Look at me, I’m a link Menu Item pointed to the following URL

http://magento.example.com/admin/pulsestorm_menututorial/index/index/key/ffcac30d7237841abc45a159390611334bb6665ce77cdf08f241e92f90688bda/

If you click on this link, Magento will redirect you to the Dashboard page. This happens because we have no URL endpoint setup for this URL! A Menu Item only generates our link. We still need to setup a MVC/MVVM endpoint for our page, which is what we’ll be doing next time!

Originally published May 8, 2016
Series Navigation<< Magento 2: Understanding Access Control List RulesMagento 2: Advanced Routing >>

Copyright © Alan Storm 1975 – 2017 All Rights Reserved

Originally Posted: 8th May 2016