Add non-category menu items to a Magento 2 shop

In Magento 2 it seems to be difficult to add something to the menubar that isn’t a category, like a static page such as “About Us”. Well, I can tell you how to do it! There’s a quick and easy fix, and there’s a ‘good’ fix.

Quick and dirty
The quick and dirty fix would be to just find the right template file for the header, and add your code after the part where the categories are called. In example, you can find the default topmenu file for the Blank theme at vendor/magento/module-theme/view/frontend/templates/html/topmenu.phtml. Copy this to your own theme and edit it to include your menu items like this:

<nav class="navigation" role="navigation">
<ul data-mage-init='{"menu":{"responsive":true, "expanded":true, "position":{"my":"left top","at":"left bottom"}}}'>
<?php /* @escapeNotVerified */ echo $_menu; ?>
<li>My extra menu item 1</li>
<li>My extra menu item 2</li>
<li>Etc..</li>
</ul>
</nav>

This would do the trick, but it’s hardcoded and not very dynamic nor does it fit in the Magento philosophy and best practice.

The right way
Let me start off by saying I hope this still isn’t the best way to do it, because it’s still pretty far fetched. As far as I know Magento has made it really difficult to add non-category items to the menu. The best way to do so anyway is, of course, to make a observer module who’ll do it ‘for you’ at the page_block_html_topmenu_gethtml_before event.

Create a module at app/code/[Namespace]/[Module] with these contents:

etc/module.xml – the module declaration file

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="[Namespace]_[Module]" setup_version="2.0.0">
<sequence>
<module name="Magento_Theme"/>
</sequence>
</module>
</config>

registration.php – the registration file

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    '[Namespace]_[Module]',
    __DIR__
);

etc/frontend/events.xml – evens declaration

<?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="page_block_html_topmenu_gethtml_before">
        <observer name="[namespace]_[module]_observer" instance="[Namespace]\[Module]\Observer\Topmenu" />
    </event>
</config>

Observer/Topmenu.php – where the actual magic happens

<?php
namespace [Namespace]\[Module]\Observer;
use Magento\Framework\Event\Observer as EventObserver;
use Magento\Framework\Data\Tree\Node;
use Magento\Framework\Event\ObserverInterface;
class Topmenu implements ObserverInterface
{
/**
* @param EventObserver $observer
* @return $this
*/
public function execute(EventObserver $observer)
{
/** @var \Magento\Framework\Data\Tree\Node $menu */
$menu = $observer->getMenu();
$tree = $menu->getTree();
$data = [
'name' => __('Menu label'),
'id' => 'unique-id',
'url' => 'url-here',
'is_active' => 0
];
$node = new Node($data, 'id', $tree, $menu);
$menu->addChild($node);
return $this;
}
}

source so far.

This would do it, if you want to add one item to the menu, if you want to add more, you’ll have to edit the public function in the above file, like so:

$data1 =
[
'name' => __('Branches'),
'id' => 'menu-branches',
'url' => '/branches',
'is_active' => 0
];
$data2 = [
'name' => __('news'),
'id' => 'menu-news',
'url' => '/news',
'is_active' => 0
];
$data3 = [
'name' => __('About Us'),
'id' => 'menu-about',
'url' => '/about-us',
'is_active' => 0
];
$data4 = [
'name' => __('Contact'),
'id' => 'menu-contact',
'url' => '/contact',
'is_active' => 0
];

$node1 = new Node($data1, 'id', $tree, $menu);
$node2 = new Node($data2, 'id', $tree, $menu);
$node3 = new Node($data3, 'id', $tree, $menu);
$node4 = new Node($data4, 'id', $tree, $menu);

$menu->addChild($node1);
$menu->addChild($node2);
$menu->addChild($node3);
$menu->addChild($node4);

The main thing to consider here is that the new Node all need the same ID for everything to show up. Also it would be possible to use different styles of mark-up for the data and there is surely a more effective way, if you set the data as one big multi-dimensional array and loop through each data to make a new node for each and then add everything to the menu at once, but for the sake of clarity this works pretty good!