I have seen How do I create menu links programmatically? but how do I update or delete menu items in Drupal 8?

By programmatically, I mean dynamically; YAML configuration files are not going to help.

  • @4k4 "Remember that menu items are entities in D8" is perhaps what you are saying? It is helpful that you are pointing that out, if that is what you are saying.
    – Duncanmoo
    Commented May 4, 2017 at 12:16
  • I think the documentation is very helpful.
    – 4uk4
    Commented May 4, 2017 at 12:33

6 Answers 6


First you will have to load the entity. Either way works:

$menu_link = MenuLinkContent::load($menu_link_id);

or ...

$menu_link = \Drupal::entityTypeManager()->getStorage('menu_link_content')->load($menu_link_id);

Next you can update value using set() method or through the magic method __set...

$menu_link->expanded = TRUE;

To save, simply call the save() method. To delete, simply call the delete() method.

  • 1
    You are using a mlid there, might seem like I am appending an additional question but where would you get that from, ie. how do you traverse a menu's tree?
    – Duncanmoo
    Commented May 4, 2017 at 12:20
  • In which hook we need to do all these? I am creating a theme and I override menu--main.html.twig file to style the menu. This code has to go in .theme file? in which hook?
    – siddiq
    Commented Dec 11, 2017 at 1:37

I had to do some thing like ....

$result = $this->menuLinkManager->loadLinksByRoute('entity.node.canonical', ['node' => $node->id()]);
foreach ($result as $menu_item) {
  if (is_object($menu_item)) {
    $id = $menu_item->getPluginDefinition()['metadata']['entity_id'];
    $menu_link = $this->entityTypeManager->getStorage('menu_link_content')->load($id);
    $menu_link->enabled = 0;

Probably too late. I did like:

$menu_handler = \Drupal::service('menu.link_tree')
  ->load("menu-name", new \Drupal\Core\Menu\MenuTreeParameters());
foreach ($menu_handler as $key => $value) {
  if ('Name' == $value->link->getTitle()) {
    $link = $value->link;
        'enabled' => '1',
        'title' => 'New Name',
  • updateLink() was unavailable for me, but $value->link = ['uri' => 'internal:/my-path']; $value->save(); worked.
    – leymannx
    Commented Apr 24 at 21:25

This is how i delete all the links in a particular menu like footer

$old_menu_links = \Drupal::entityTypeManager()->getStorage('menu_link_content')
  ->loadByProperties(['menu_name' => 'footer']);
foreach ($old_menu_links as $old_menu_link) {

And to update existing menu,

$menu_links = \Drupal::entityTypeManager()->getStorage('menu_link_content')
  ->loadByProperties(['menu_name' => 'footer']);
foreach ($menu_links as $menu_link) {

  if ($menu_link->gettitle() == 'old-title') {
    $menu_link->set('title', 'new-title');

This turned out to be insanely complicated. Apparently not every menu link is a MenuLinkContent object. Sometimes it is a MenuLinkDefault object. In that case, you cannot use ->set() or ->save(). This is the most complete answer to cover both cases. Put this inside of an hook_update_n() and change $disabled_routes and $menu to your needs. In this example I am disabling menu links.

  $disable_routes = [

  $menu_name = 'main';

  $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
  $menu_link_tree_elements = \Drupal::service('menu.link_tree')
    ->load($menu_name, new \Drupal\Core\Menu\MenuTreeParameters());

  foreach ($disable_routes as $disable_route) {
    foreach ($menu_link_tree_elements as $key => $menu_link_tree_element) {
      $menu_link_default = $menu_link_tree_element->link;
      $plugin_definition = $menu_link_default->getPluginDefinition();
      $link_route = $plugin_definition['route_name'];
      if ($link_route == $disable_route) {
        $uuid = $menu_link_default->getDerivativeId();
        if (!empty($uuid)) {
          $menu_link_content = \Drupal::service('entity.repository')
            ->loadEntityByUuid('menu_link_content', $uuid);
          $menu_link_content->set('enabled', 0);
        else {
          $menu_link = $menu_link_manager->getDefinition($key);
          $menu_link['enabled'] = 0;
          $menu_link_manager->updateDefinition($key, $menu_link);

          // Not sure if this is necessary.
              'enabled' => 0,

My use case was, that I had a leftover menu link, when uninstalling the CAS module, due to this core issue https://www.drupal.org/project/drupal/issues/3324291. Since I could not load the menu link via \Drupal::entityTypeManager()->getStorage('menu_link_content')->load($menu_link_id);, because this entry was only in the menu_tree db table and not in menu_link_content(_data) db tables (looks like that this is because of the core issue and not standard behavior, but I didn't investigate further here), this is what I did (it was a MenuLinkDefault object and not a MenuLinkContent object for some context also):

  /** @var \Drupal\Core\Menu\MenuTreeStorageInterface $menu_tree_storage */
  $menu_tree_storage = \Drupal::service('menu.tree_storage');
  /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
  $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
  $cas_menu_link = $menu_link_manager->loadLinksByRoute('cas.settings');
  if (!empty($cas_menu_link)) {
    // Array keys are plugin IDs.

The delete function here removes the data only from the menu_tree table, keep that in mind. But it also updates all the childred/parents of the deleted link in the tree structure. If you delete MenuLinkContent entities (menu_link_content _data db tables), this process is also done via the preDelete function in the entity class, so the tree storage is also updated.

So to sum it up, this solution is for purging of orphaned data in the menu_tree table, on incorrect deletion of the MenuLinkContent entities, due to a core issue.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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