Drupal 8 Routing: Decoupling hook_menu

May 27
blog author


Drupal Developer

Drupal 8 is changing the way Drupal developers deliver content to the browser for a particular web page. It replaces parts of hook_menu() with Symphony2's HTTP Kernel. This approach is more object oriented and allows developers to decouple functionality which will make applications more flexible and maintainable.

The Drupal 7 way

Let's take a look at how we would define a route in Drupal 7. Lets say we want to add a new page to our site. In Drupal 7, we need to define an item in hook_menu(), that is an array, with the route as the array key, that defines certain things Drupal needs to know in order to figure out what content to send.

 * Implements hook_menu().
 function routing_example_menu() {
   $items = array();

   $items['routing_example/example_page/%/%'] = array(
   	'title' => 'My Example Page',
    'page callback' => 'routing_example_example_page',
    'page arguments' => array(2, 3),
    'access callback' => 'routing_example_access_callback',
    'access arguments' => array('authenticated user'),
    'type' => MENU_NORMAL_ITEM,
    'menu_name' => 'primary-links',
    'weight' => -5,
    'file' => 'routing_example.module',
   return $items;

There are a lot of different things going on here: we are defining the function that will return the content; which arguments are passed to the function; where that function is located; who can see the content; what type of link it is; which menu the link shows in (if any); and, where in the menu it is displayed. That's a lot of information. It tightly couples together the route definition and the definition for the menu link, which are two different things. We could also not have included a menu link by making the type MENU_CALLBACK. So now you can have some items that are just routes, some items that are links, and some items that are both. Hook_menu() is trying to do too many things at once and it can get confusing.

The Drupal 8 way

Drupal 8 solves this problem by breaking out the route definition from the link definition. We now use one yaml file to define routes (module_name.routing.yml), and another to define menu links (module_name.menu_links.yml).


Routing Yaml File

The main parts of the routing yml file:

  • module.route: machine name for the route
  • path (required): is the URL for the route. This would have been your array key in Drupal 7
  • defaults(required): defines how your content is delivered
  • requirements (required): determines conditions that need to be set to allow access
  • options (optional): additional options that will impact the route

  path: 'routing_example/example_page/{name}/{title}'
    _content: '\Drupal\routing_example\Controller\ExampleController::examplePage'
    custom_arg: 'custom'
    _role: 'authenticated user'

Here we've defined our path, and the function that will return. Drupal now uses PSR-4 convention so that your classes are automatically loaded when called. For your controller class to be discovered by Drupal it needs to live inside your module directory in:


and define its namespace as:

namepace Drupal\module_name\Controller

The Controller

Our controller code will look like this:

* @file
* Contains \Drupal\routing_example\Controller\ExampleController.
namespace Drupal\routing_example\Controller;

* Example page controller.
class ExampleController {

public function examplePage($custom_arg, $name = NULL, $title = NULL){
  $build = array(
    '#markup' => t("<p>Hello $name $title!</p><p>Custom argument is: $custom_arg</p>"),
  return $build;

    return $build;

The examplePage function in our ExampleController will be called when someone visits that URL, and will return our content to the browser. It will also receive three arguments: the custom arg we defined in the defaults, as well as the two arguments we defined in the route. Instead of the old wild card convention (%) in Drupal 7, Drupal 8 uses brackets{} with the argument name. In the requirements section we specify that the user must have the role 'authenticated user' to see this page. Drupal 8 allows you to provide or deny access based on roles, rather than having to define a custom function to restrict pages based on a role. You can find out more information about which options are available for your routing files here.

Drupal 8 was originally going to replace hook_menu() with another hook, hook_default_menu_links(), but the decision was made to move link definitions to yaml files as well. You can read more about that here: Providing module-defined menu links.


This is a quick introduction to Drupal 8's new routing system. We showed how Drupal 8 decouples different tasks that were all handled by hook_menu() in Drupal 7. For more information checkout the documentation page at drupal.org: https://drupal.org/developing/api/8/routing.