Managing navigation is a core part of building any Drupal site. While the administrative interface provides a robust way to manage menus, there are many scenarios where you need to create menu links programmatically. Whether you are performing a data migration, building a custom module that requires specific navigation, or automating site updates via deployment scripts, knowing how to handle the Drupal Menu API is essential.
In this guide, we will explore the different ways to create menu links programmatically in Drupal 8, 9, and 10. We will cover dynamic creation using the MenuLinkContent entity, static definitions via YAML files, and how to organize links into hierarchies.
Using the MenuLinkContent Entity for Dynamic Links
When you need to create menu links that behave like content—meaning they can be edited by site administrators through the UI later—you should use the MenuLinkContent entity. This approach is most common when you are running a one-time script or using hook_update_N() in a .install file to update a site's structure during deployment.
To use this method, you first need to ensure you are importing the correct namespace. Here is how you can create multiple menu links pointing to specific nodes:
/**
* Put the following line on the top of the file containing the
* following code.
*/
use Drupal\menu_link_content\Entity\MenuLinkContent;
$items = [
'1' => 'Menuitem 1',
'2' => 'Menuitem 2',
'3' => 'Menuitem 3'
];
foreach($items as $nid => $title) {
$menu_link = MenuLinkContent::create([
'title' => $title,
'link' => ['uri' => 'internal:/node/' . $nid],
'menu_name' => 'main',
'expanded' => TRUE,
]);
$menu_link->save();
}
In this example, the uri uses the internal: scheme. This is the standard way to reference internal Drupal paths. The menu_name identifies which menu the link belongs to (e.g., main for the Main Navigation, admin for the Management menu, or footer for the Footer menu).
Programmatically Creating a New Menu
Sometimes, simply adding a link to an existing menu isn't enough; you might need to create an entirely new menu container. This is handled by the menu configuration entity. You can use the entityTypeManager service to create and save a new menu definition.
$menu = \Drupal::entityTypeManager()
->getStorage('menu')
->create([
'id' => 'menu_test',
'label' => 'Test menu',
'description' => 'Description text',
]);
$menu->save();
Once this menu is saved, it will appear in the Drupal administrative interface under Structure > Menus, and you can begin attaching links to it using the menu_name of menu_test.
Defining Static Menu Links via YAML
If your module provides a specific page (like an admin settings form or a custom dashboard) and you want a menu link to exist as long as the module is enabled, the best practice is to use a YAML file. This is known as a "module-defined" menu link.
Create a file named custom_module.links.menu.yml in your module's root directory:
custom_module.admin_item_1:
title: 'New Admin Item 1'
parent: system.admin
description: 'Description of link goes here.'
route_name: view.some_view_id.page_1
weight: 10
Key YAML Properties:
- parent: (Optional) The ID of the parent menu link. For example,
system.adminplaces your link inside the main Administration menu. - route_name: The internal Drupal route ID. If you are linking to a View, it usually follows the pattern
view.view_id.display_id. - weight: Determines the order of the link relative to others in the same menu level.
Static links defined in YAML are not stored in the database as content entities, which makes them easier to version control and deploy across environments.
Handling Nested Menu Hierarchies
Creating a flat list of links is straightforward, but real-world navigation often requires nesting. To nest a menu item inside another programmatically, you must identify the parent link's ID and pass it into the parent property of the child link.
Here is an example of how to programmatically find a parent link within a specific menu and attach a new child link to it:
$menu_link_storage = \Drupal::entityTypeManager()->getStorage('menu_link_content');
// Load links from a specific menu to find a potential parent
$my_menu = $menu_link_storage->loadByProperties(['menu_name' => 'my-menu-name']);
$top_level = NULL;
foreach ($my_menu as $menu_item) {
$parent_id = $menu_item->getParentId();
if (!empty($parent_id)) {
$top_level = $parent_id;
break;
}
}
// Create the child link
$menu_link_storage->create([
'title' => 'My menu link title',
'link' => ['uri' => 'internal:/my/path'],
'menu_name' => 'my-menu-name',
'parent' => $top_level,
'expanded' => TRUE,
'weight' => 0,
])->save();
By setting the parent key to the ID of another menu link entity, Drupal automatically handles the tree structure and rendering of sub-menus.
Frequently Asked Questions
Can I link to external URLs programmatically?
Yes. When using the MenuLinkContent entity, simply change the uri from internal:/path to a full URL like https://example.com. Drupal will recognize this as an external link.
How do I delete a programmatically created menu link?
If you have the ID of the menu link, you can load it and call the delete method:
\Drupal\menu_link_content\Entity\MenuLinkContent::load($id)->delete();. If you created the link via YAML, simply remove the entry from the YAML file and clear the cache.
What is the difference between route_name and uri?
route_name is used for internal Drupal routes defined in *.routing.yml files. It is the preferred method for internal links because it remains valid even if the URL path changes. uri is more flexible and can be used for internal paths, external URLs, or special schemes like entity:node/1.
Wrapping Up
Creating menu links programmatically in Drupal offers a powerful way to automate your site's architecture. For permanent, module-based links, stick with *.links.menu.yml. For dynamic, content-driven links or one-time updates, use the MenuLinkContent entity within a hook_update_N() function.
As Drupal continues to evolve, these core methods remain the standard. Always remember to clear your cache after modifying YAML files or running update scripts to ensure your new navigation elements appear correctly for your users.