Menu system
This chapter introduces the Drupal menu system used for Symfony-based Drupal. It describes how to to make forms and pages with content created using code accessible through menu routes.
Table of contents
- Introduction
- Changes from earlier version of Drupal
- Admin toolbar
Some of this is based upon Drupal 7 and needs QA and updating. The section not updated is marked [D7].
Introduction
In this chapter will introduce the menu system used for Symfony-based Drupal (i.e. Drupal 8 and later). This menu system facilitates site navigation by means of routes.
The menu system drives both site navigation from a user perspective and the system used to respond to URLs passed from the browser.
You may put items into the existing menus on the website by means of the administrative GUI. To do so, navigate to
and under “Operations”, pull down the menu and click “Add link” for the menu you want to modify.The menu system follows a simple hierarchy defined by routes. A route is always assigned to a path (which should be unique). The menu system aggregates these items and determines the menu hierarchy from the paths.
For example, if the paths defined were:
a
,a/b
,e
,a/b/c/d
,f/g
,a/b/h
, the menu system would form the structure:- a
- a/b
- a/b/c/d
- a/b/h
- a/b
- e
- f/g
Modules that wants to display page content that can be addressed by a URL in Drupal must register a route for it. Registering a route means defining a menu router entry for the page.
To define your menu router entry, first you will need to choose a path. All paths originate at your site's base URL.
Drupal stores menu router items in a configuratuion file that starts with the module's short name and ends with
.router.yml
.Changes from earlier version of Drupal
In Drupal 8,
hook_menu()
was deprecated. The menu system now live in the following five configuration files:*.routing.yml
; Linking routes to paths and permissions.*.links.menu.yml
: A “normal” menu item that appears in a menu and in breadcrumbs.*.links.tasks.yml
: A task specific to the parent item (e.g.node/52/edit
), usually rendered as a tab above the parent item.*.links.actions.yml
: An action specific to the parent.*.contextual.tasks.yml
: Contextual links.
If a route already exists (e.g. a page view), only the second is required. For other small modules, only the first two are used to set up a route to the page linked to. The last two (action and contextual links) are seldom used.
The diagram below shows the differences between the Drupal 7 and Drupal 8 and later.
To learn how to upgrade menus to Symfony-based Drupal, see this upgrade tutorial at DO: Convert hook_menu() and hook_menu_alter() to Drupal 8 and later APIs. For futher reference, see the implementation of menus in the contributed Notify module.
Admin toolbar
How can I add items to the Admin Toolbar?Configuration menu
In addition to the menus that can be accessed in the GUI by navigating to
or programatically added to a named menu, Drupal projects can set up menus for a project's configuration page.To get a configuration menu linked to from the site's modules overview page (reached by navigating to
in the administration menu), you have to have the named route to its configuration assigned to theconfigure
key in the project's*.info.yml
-file.Example
mymodule.info.yml
:name: 'My module' description: 'Control visibility of teaserlinks.' configure: mymodule.config core_version_requirement: ^8.8 || ^9 || ^10 type: module
The path to the module's configuration form is the
*.routing.yml
. The example below places the link to the configuration form in the "System" section of the "Configuration page":Example
mymodule.routing.yml
:mymodule.config: path: /admin/config/system/mymodule defaults: _title: 'My Module' _form: \Drupal\mymodule\Form\MymoduleConfigForm requirements: _permission: 'administer site configuration'
A project may create several menus, but there should only be one route to its configuation form.
Then create an entry for the link to the form in
*.links.menu.yml
. Theroute_name
key is the name of the route just created. Theparent
key indicates where to place it. In the example below, it is in the "System" section on the administrative configuration page.Example
mymodule.links.menu.yml
:mymodule.config_form: title: 'My module' description: 'Configuration form for My Module.' route_name: mymodule.config parent: system.admin_config_system weight: 10
Source DO:Add a menu link.
There is no way to guess the name of the parent section on the configuration page. For example, we have for the sections "SYSTEM" and "PEOPLE" on the configuration page:
PEOPLE: parent: user.admin_index SYSTEM: parent: system.admin_config_system
To find these names, install Devel and navigate to
. You may also examine the*.links.menu.yml
of a contributed or core project appearing in the section.To learn how to create a new parent, see this question on DSE: How to create a new parent section on the Configuration page?.
ToDo
Add these sections:
- a
- Configuration form
- Tabs for submenus (Notify)
Routes defined by views
If a route to the page already exists, for example, it has been defined by a page view, adding it to the navigtion system is trivial. Below are examples showing how to place a link to a page view on the "Reports" page and in the "Tray".
An extension named Project Documentation
[projectdocumentation
] comes with a view that is part of
the confoguration. The route to this view
is view.project_list.page_1
.
The following entry
in projectdocumentation.links.menu.yml
will put a link to
it on the "Reports" page. To see it, navigate
to :
projectdocumentation.report: title: 'Project list' description: 'Projects registered for this website.' route_name: view.projectlist.page_1 parent: system.admin_reportsTo have the link in the tray (the white line below the black administrative toolbar), use this entry
system.admin_projectdocumentation: title: 'Project list' description: 'Description.' route_name: 'view.project_list.page_1' parent: 'system.admin' weight: 0
Creating a link in the black toolbar without using the GUI is harder. You may want to read DSE: How can I add items to the Admin Toolbar?
Access control
Access control for a menu is specified in the .routing.yml
-file. The example below
requires the user to have the access content
permission to access the form:
hello_world.content: path: '/hello' defaults: _controller: '\Drupal\hello_world\Controller\HelloController::content' _title: 'Hello World' requirements: _permission: 'access content'
Source DO: Add a routing file.
If
you don't want to specify permissions that are specific to your module, you may want
to consider reusing the following core permissions (hopefully the intended use is obvious):
“access content
” (can be given to all roles),
“administer site configuration
” (give to trusted roles only),
“administer users
” (give to trusted roles only).
If a permission should only be given to trusted roles, add the
restrict_access
key set to TRUE
to the
relevant permision set up in the
project's *.permissions.yml
:
Example: mymodule.permissions.yml
:
view mymodule secret: title: 'Administer secret text protected by My Module' description: 'Alter the special secret text presented to new members.' restrict_access: TRUE
If the key is missing, it defaults to FALSE
.
Final word
[TBA]
Some legacy D7 materials
The most common type of menu gives the function
drupal_get_form()
as the callback, and gives the name of the function to implement the
business logic for the form in the first page argument.
Below is an example of a menu to access an administrative menu with
two tabs. To add more tabs, just reuse the boilerplate for the last
item. If no tabs are required, only the first item
(MENU_NORMAL_ITEM
) is required.
/** * Implements hook_menu(). */ function mymodule_menu() { $items = array(); $items['admin/config/people/example-menu'] = array( 'title' => 'Example', 'description' => 'This description shows up on the configuration page.', 'page callback' => 'drupal_get_form', 'page arguments' => array('mymodule_tab1'), 'access callback' => TRUE, 'file' => 'mymodule.admin.inc', 'type' => MENU_NORMAL_ITEM, ); $items['admin/config/people/example-menu/not-shown] = array( 'title' => 'First tab', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => 0, ); $items['admin/config/people/example-menu/second'] = array( 'title' => 'Second tab', // No description required. 'page callback' => 'drupal_get_form', 'page arguments' => array('mymodule_tab2'), 'access callback' => TRUE, 'file' => 'mymodule.admin.inc', 'type' => MENU_LOCAL_TASK, 'weight' => 1, ); return $items; }
The MENU_DEFAULT_LOCAL_TASK
is the default tab shown
at the path pointed to by MENU_NORMAL_ITEM
. The path for
the MENU_DEFAULT_LOCAL_TASK
must be different
from the path for MENU_NORMAL_ITEM
, but it is not
displayed to the user.
Below are the boilerplate callback functions for the above menu.
/* * Menu callback: Provide first tab page for MyModule. * * @return array * Form. */ function mymodule_tab1() { $form['mymodule_msg'] = array( '#markup' => t('This is the first tab'), ); return $form; } /* * Menu callback: Provide second tab page for MyModule. * * @return array * Form. */ function mymodule_tab2() { $form['mymodule_msg'] = array( '#markup' => t('This is the second tab'), ); return $form; }
Custom callback [D7]
Instead of using the predefined callback function
drupal_get_form()
,
you may write a custom callback function. You will typically do this
if the menu does not link to a form, but to content you want to create
programmatically, rather than by using the GUI to create a node.
To pass arguments to the callback function, use
page arguments
.
The page arguments may be strings:
function mymodule_menu() { $items['helloworld'] = array( 'title' => 'Hello world', 'page callback' => 'mymodule_custom', 'page arguments' => array('wonderful', 'crazy'), 'access callback' => TRUE, 'file' => 'mymodule.admin.inc', 'type' => MENU_NORMAL_ITEM, ); return $items; }
A callback function that outputs the aguments:
function mymodule_custom($arg1, $arg2) { $content['raw_markup'] = array( '#type' => 'markup', '#markup' => '<p>Hello, ' . $arg1 . '! It is a ' . $arg2 . ' world.</p>', ); return $content; }
To pass the arguments as part of the path, use numeric
page arguments
:
function mymodule_menu() { $items['helloworld/%/%'] = array( 'title' => 'Hello world', 'page callback' => 'mymodule_custom', 'page arguments' => array(1,2), 'access callback' => TRUE, 'file' => 'mymodule.admin.inc', 'type' => MENU_NORMAL_ITEM, ); return $items; }
Given the same callback function, the following path will produce an identical output as the previous example.
/helloworld/wonderful/crazy
Last update: 2021-10-16 [gh].