I need to add some elements in the admin menu based on some conditions (custom logic) so I cannot use adminhtml/menu.xml for that. (or can I?)

For example, I need to add inside the products main menu the next structure

  • Accessories
    • Accessories type 1
    • Accessories type 2.
    • ...
    • Accessories type N.

Numbers 1 to N are dynamic and may come from different extensions that link to the same class.
Is there an event I can hook on? Or some class I can pluginize?

  • you want to create ACL rule?
    – Avesh Naik
    Jul 30 '18 at 12:59
  • That will be the next step, but for now I'm not concerned with this. I just need to display multiple elements based on code not just xml configuration.
    – Marius
    Jul 30 '18 at 13:00
  • Check this solution. magento.stackexchange.com/questions/1333/…
    – Avesh Naik
    Jul 30 '18 at 13:04
  • Already saw that. That's for magento 1. The admin menu structure changed in Magento 2.
    – Marius
    Jul 30 '18 at 13:05
  • I had this type of requirement, I looked for core code, Found something related to it. But Too lazy to start So kicked it. Check this Hope it will help you. github.com/magento/magento2/blob/2.2-develop/app/code/Magento/… Damm Sure. You already reached above !!
    – anonymous
    Jul 31 '18 at 6:22

The solution for Magento 1 mentioned by @avesh in the comments above also applies to Magento 2. You can still listen to event adminhtml_block_html_before. The sample code below adds a "Test Menu" under "Catalog".


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="adminhtml_block_html_before">
        <observer name="Vendor_Module_Custom_Menu" instance="Vendor\Module\Observer\AdminhtmlBlockHtmlBefore" />


namespace Vendor\Module\Observer;

use Magento\Framework\Event\ObserverInterface;

class AdminhtmlBlockHtmlBefore implements ObserverInterface
    protected $menuItemFactory;

    public function __construct(
        \Magento\Backend\Model\Menu\Item\Factory $menuItemFactory
    ) {
        $this->menuItemFactory = $menuItemFactory;

    public function execute(\Magento\Framework\Event\Observer $observer)
        $block = $observer->getBlock();

        if($block instanceof \Magento\Backend\Block\Menu){
            $menuModel = $block->getMenuModel();

            $itemData = array(
                'id'          => 'testMenuId',
                'title'       => 'Test Menu',
                'resource'    => 'Magento_Catalog::products',               
                'action'      => 'module/action/'

            $item = $this->menuItemFactory->create($itemData);
            $menuModel->add($item, 'Magento_Catalog::inventory', 100); //$menuModel->add($item, $parentId, $index)
  • This looks like it should work. +1 from me, but what I don't like is the fact that it hooks into a general method, called for a lot of classes. But indeed it can be used as a last resort. It's pretty clean.
    – Marius
    Jul 31 '18 at 6:45
  • 2
    Thanks @Marius, I learnt a lot from your posts. You're like the god of Magento. Jul 31 '18 at 6:49

I think I found a nice, clean solution.
The idea is to pluginize Magento\Backend\Model\Menu\Builder::getResult().
I've added this in the adminhtml/di.xml

<type name="Magento\Backend\Model\Menu\Builder">
    <plugin name="add-dynamic-menu" type="[Vendor]\[Module]\Plugin\Model\Menu\BuilderPlugin" />

Then my plugin looks like this:

namespace [Vendor]\[Module]\Plugin\Model\Menu;

use Magento\Backend\Model\Menu\Builder;
use Magento\Backend\Model\Menu;
use Magento\Backend\Model\Menu\ItemFactory;

class BuilderPlugin
     * @var ItemFactory
    private $menuItemFactory;

    //... other members here

     * BuilderPlugin constructor.
     * @param ItemFactory $menuItemFactory
    public function __construct(
        ItemFactory $menuItemFactory,
        //.... other dependencies here
    ) {
        $this->menuItemFactory = $menuItemFactory;
        //....other assignments here

     * @param Builder $subject
     * @param Menu $menu
     * @return Menu
    public function afterGetResult(Builder $subject, Menu $menu)
        if (my condition here) {
            /** @var Menu\Item $item */
            $parent = 'Magento_Catalog::catalog'; //adding the menu as part of the catalog main menu
            $item = $this->menuItemFactory->create([
                'data' => [
                    'parent_id' => $parent,
                    'id' => '[Vendor]_[Module]::some_key_here',
                    'title' => 'Menu title here',
                    'resource' => '[Vendor]_[Module]::some_key_here' 
            $menu->add($item, $parent);
            ///add submenu for the menu item added above
            foreach (loop through my dynamic list as $dynamicItem) {
                $item = $this->menuItemFactory->create([
                    'data' => [
                        'parent_id' => '[Vendor]_[Module]::some_key_here', //id of menu above
                        'id' => '[Vendor]_[Module]::some_key_here_'.$dynamicItem->getCode(), //give it a unique id
                        'title' => $dynamicItem->getTitle(), //title of the submenu
                        'resource' => '[Vendor]_[Module]::some_key_here', //same ACL key as above, or it can be different
                        'action' => $dynamicItem->getUrl() //url for the main menu
                $menu->add($item, [Vendor]_[Module]::some_key_here'); //add is as a child for the menu item above

        return $menu;

Check this one https://gist.github.com/herveguetin/7c78e3991687744f164b

Convert this line to M2

$menu = Mage::getSingleton('admin/config')->getAdminhtmlConfig()->getNode('menu/MAIN_MENU_ITEM/children/MENU_ITEMS_CONTAINER/children');
  • This won't work. The config files in magento 2 are not merge into one huge config. They are separated by role.
    – Marius
    Jul 30 '18 at 15:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.