---
title: "Drupal 8 Routing: Decoupling hook_menu · Appnovation"
url: "https://www.appnovation.com/insights/drupal-8-routing-decoupling-hookmenu"
description: "How Drupal 8 routing replaces hook_menu() with Symfony's HTTP Kernel, and how to decouple functionality with an object-oriented approach."
---

Article

# Drupal 8 Routing: Decoupling hook\_menu

[![](https://cdn.sanity.io/images/ey850001/production/d536b2db75ac7bb86176f5bb947d39f316b496ae-68x68.png?w=68&auto=format) kmoll kmoll Drupal Developer](/people/kmoll-kmoll.md) May 27, 2014  
 4 min read 

![Drupal 8 Routing: Decoupling hook_menu](https://cdn.sanity.io/images/ey850001/production/8203f3e6a8c53d3b9062e078b2474025cffc62e4-700x200.jpg?w=1400&auto=format) 

Drupal 8 is changing the way [Drupal developers](/services/drupal-development.md) 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

routing\_example.page: path: 'routing\_example/example\_page/{name}/{title}' defaults: \_content: '\\Drupal\\routing\_example\\Controller\\ExampleController::examplePage' custom\_arg: 'custom' requirements: \_role: 'authenticated user' 

Here we've defined our path, and the function that will return. Drupal now uses[PSR-4 convention (opens in new window)](http://www.php-fig.org/psr/psr-4/) 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:

\[module\_directory\]/src/Controller/ 

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. (opens in new window)](https://drupal.org/node/2092643)

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 (opens in new window)](https://drupal.org/node/2122241).

**Summary**

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 (opens in new window)](https://drupal.org/developing/api/8/routing).

[![](https://cdn.sanity.io/images/ey850001/production/d536b2db75ac7bb86176f5bb947d39f316b496ae-68x68.png?w=68&auto=format) kmoll kmoll Drupal Developer · Appnovation](/people/kmoll-kmoll.md) 

## Keep reading

[![What is Sovereign AI](https://cdn.sanity.io/images/ey850001/production/a5889abdd5e80376912546622e046e3fbef234b4-2496x1664.png?w=640&auto=format)](/insights/what-is-open-source-sovereign-ai.md)

 Article · 7 min AI 

### [What Is Open Source, Sovereign AI?](/insights/what-is-open-source-sovereign-ai.md)

Every company is encouraging more AI, and almost every AI bill is climbing. Sovereign AI is about two kinds of control: over your data and over your costs. Here's why open source sits at its foundation.

[Iain MacNeil](/people/iain-macneil.md) Article 

[![AI-first experience design](https://cdn.sanity.io/images/ey850001/production/8bbc2418e87924ef10f36115478a2e10d5bed95e-1584x624.jpg?w=640&auto=format)](/insights/how-build-ai-first-experience-without-throwing-out-everything-youve-already-built.md)

 Article · 6 min AI Sep 10, 2026 

### [How to Build an AI-First Experience Without Throwing Out Everything You've Already Built](/insights/how-build-ai-first-experience-without-throwing-out-everything-youve-already-built.md)

Users now expect a direct answer, not a list of links to click through. Here's how organizations can layer an AI-First experience onto their existing website or app, in weeks rather than years.

[Andrew Douglas](/people/andrew-douglas.md) Article 

[![What Becomes Possible When AI Can Read All the Data](https://cdn.sanity.io/images/ey850001/production/c28ab0bd26753421ee42937e13adc7a7f19b47f0-1296x510.jpg?w=640&auto=format)](/insights/what-becomes-possible-when-ai-can-read-all-data.md)

 Article · 4 min AI Sep 09, 2026 

### [What Becomes Possible When AI Can Read All the Data](/insights/what-becomes-possible-when-ai-can-read-all-data.md)

Most of the world's data has never been analyzed, only sampled. AI changes that. How AI-ready data platforms unlock questions no one could ask before.

[Appnovation](/people/appnovation.md) Article 

## Get the thinking,  
*before everyone else.*

One considered email a month. New essays, field notes from live engagements, and the occasional strong opinion. No noise.
