Menu links with dynamic values

The menu link defined statically in a *.links.menu.yml file can be altered with the hook menu links discovered alter hook, but if you want to provide a dynamic value you have to use something else.

Let's see first how to alter a statically defined menu link.

You can use hook_menu_links_discovered_alter() hook to hide or alter some properties of a menu link. For example, you can hide the Home menu item in the Main navigation like this:

/**
 * Implements hook_menu_links_discovered_alter().
 */
function MY_MODULE_menu_links_discovered_alter(&$links) {
  unset($links['standard.front_page']);
}

By the way, the Home menu item is defined in the Standard profile here:

standard.links.menu.yml

To alter the title you can do something like this:

/**
 * Implements hook_menu_links_discovered_alter().
 */
function MY_MODULE_menu_links_discovered_alter(&$links) {
  $links['standard.front_page']['title'] = t('Altered Title');
}

As you can see hook menu links discovered alter hook is very useful, but it's not the right place to implement dynamic behavior. For that, you have to use the Menu link plugin class.

Let's say that you want to show or hide a menu item depending on the current user role. You can do it this way:

MY_MODULE.links.menu.yml

my_module.user:
  title: 'User'
  description: 'Dynamic menu item.'
  route_name: 'my_module.user'
  parent: 'system.admin'
  class: Drupal\my_module\Plugin\Menu\UserMenuLink

src/Plugin/Menu/UserMenuLink.php

<?php

namespace Drupal\MY_MODULE\Plugin\Menu;

use Drupal\Core\Menu\MenuLinkDefault;

class UserMenuLink extends MenuLinkDefault {

  /**
   * {@inheritdoc}
   */
  public function isEnabled() {
    $roles = \Drupal::currentUser()->getRoles();

    if (!in_array('administrator', $roles)) {
      return FALSE;
    }

    return TRUE;
  }

}

Or if you want to provide a dynamic menu title you can do it this way:

<?php

namespace Drupal\MY_MODULE\Plugin\Menu;

use Drupal\Core\Menu\MenuLinkDefault;

class UserMenuLink extends MenuLinkDefault {

  /**
   * {@inheritdoc}
   */
  public function getTitle() {
    $roles = \Drupal::currentUser()->getRoles();
    $roles = implode(', ', $roles);
    
    return \Drupal::currentUser()->getDisplayName() . ' - ' . $roles;
  }

}

And to conclude this blog post just a reminder that you should use Dependency Injection to inject the current user if you need it. I used the global Drupal class to make my code shorter.

About the Author

Goran Nikolovski is a web and AI developer with over 10 years of expertise in PHP, Drupal, Python, JavaScript, React, and React Native. He founded this website and enjoys sharing his knowledge.