Routes and menu items are a critical part of the Drupal system. Without them, users would not be able to access the content of a website or any other page. Conceptually Drupal 7 and 8 are the same: map a URL to a function that is responsible for getting and returning the content. But the way it is implemented is completely different (as is the case with most things under the hood between Drupal 7 and 8).

If you are totally new to Drupal 8, you might want to checkout my tutorial on Understanding Drupal 8 routes and controllers as well.

Basic Menu and Routes in Drupal 7

In Drupal 7, you add a route and menu item in the same place by implementing hook_menu. You can then map the menu item to a callback function.

/**
 * Implements hook_menu(). 
 */
function first_module_menu() {
  $items['first/custom'] = array(
	'title' => 'Custom page',
	'page callback' => 'first_module_custom', 
	'access arguments' => array('access content'),
  );

  return $items;
}

In the above snippet, first/custom is the path, which is mapped to the callback function called first_module_custom. In other words, if your Drupal site’s domain was example.com, if you go to example.com/first/custom, you could get the content returned from the callback function first_module_custom.

A very simple callback function would look like this:

function first_module_custom() {
  $content = t('Hello world');

  return $content;
}

Basic Menu and Routes in Drupal 8

To do the same thing in Drupal 8, you would need to create a route, utilising the new routing system in Drupal 8 and separately register the menu item.

Routes in Drupal 8

In Drupal 8, routes are based on Symfony’s routing system. Routes are defined in their own YAML file in the root of the module folder and take the format modulename.routing.yml

first_module.content:
  path: '/first/custom'
  defaults:
    _controller: 'Drupal\first_module\Controller\FirstController::content'
    _title: 'Custom page'
  requirements:
    _permission: 'access content'

This maps the URL path for /first/custom to a controller, which now acts as the page callback.

A simple controller to do the same thing as the call back in the Drupal 7 example would look like this:

class FirstController extends ControllerBase {

  public function content($name) {
    return array(
      '#type' => 'markup',
      '#markup' => $this->t('Hello world'),
    );
  }
}

To register the path as a menu link in Drupal 8, you need to create a YAML file for your module, using the modulename.menu.yml naming convention (replace modulename with the actual module name). Here is an example:

first_module.demo:
  title: 'Custom page'
  route_name: first_module.content
  description: 'Hello world message on custom page'
  parent: main
  menu_name : main

Arguments

You can pass arguments from the URL to the callback function, which can then be used to affect the content returned. In the examples above, we registered the path of ‘first/custom’. You could allow users to go to ‘first/custom/123’, where 123 is passed to the callback.

Arguments in Drupal 7

In Drupal 7, you would do this by adding a wildcard (%) to the end of the registered path and adding a page arguments property.

/**
 * Implements hook_menu(). 
 */
function first_module_menu() {
  $items['first/custom/%'] = array(
	'title' => 'Custom page',
	'page callback' => 'first_module_custom', 
	'page arguments' => array(1),		  
	'access arguments' => array('access content'),
  );

  return $items;
}

The callback function can then take that argument and use it to affect the content returned.

function first_module_custom($name) {
  // View of node.
  $content = t('Hello @name', array('@name' => $name));

  return $content;
}

You can call the parameter in first_module_custom anything, it doesn’t have to be $name.

Arguments in Drupal 8

In Drupal 8, you can add a similar wildcard, this time with a named placeholder (otherwise known as a slug). In the route below the placeholder ‘{name}’ has been added to the path.

first_module.content:
  path: '/first/{name}'
  defaults:
    _controller: 'Drupal\first_module\Controller\FirstController::content'
    _title: 'Hello world'
    name: 'Bob'
  requirements:
    _permission: 'access content'

This is passed to the controller as an argument. In the example below, the argument is a variable called $name in the welcome method(). It is then used in $this->t().

<?php
/**
 * @file
 * Contains \Drupal\first_module\Controller\FirstController.
 */

namespace Drupal\first_module\Controller;
use Drupal\Core\Controller\ControllerBase;

class FirstController extends ControllerBase {

  public function content($name) {
    return array(
      '#type' => 'markup',
      '#markup' => $this->t('Hello %name', array('%name' => $name)),
    );
  }
}

?>

For more information on using arguments in Drupal 8 routes, check out - making a Drupal 8 route dynamic.

Permissions

Permissions allow you to restrict who can access menu items. Specific user roles have access to particular permissions.

Permissions in Drupal 7

Use ‘access arguments’ in Drupal 7 to restrict access to a path.

In the following example, access to ‘first/custom’ is given to anyone who has been granted the ‘access content’ permission, which on most Drupal sites will be everyone.

/**
 * Implements hook_menu(). 
 */
function first_module_menu() {
  $items['first/custom'] = array(
	'title' => 'Custom page',
	'page callback' => 'first_module_custom', 
	'page arguments' => array(1),  
	'access arguments' => array('access content'),
  );

  return $items;
}

Permissions in Drupal 8

In Drupal 8, the permissions are granted in the route YAML file using the requirements and _permission keys.

first_module.content:
  path: '/first/{name}'
  defaults:
    _controller: 'Drupal\first_module\Controller\FirstController::content'
    _title: 'Hello world'
    name: 'Bob'
  requirements:
    _permission: 'access content'

Defining your own permission

In the above examples, ‘access content’ is a permission that is already defined in every Drupal site. You can create your own permission to give you more control.

After you have defined the permission, you can grant access to it for particular roles in the admin section (Admin > People > Permissions).

Defining your own permission in Drupal 7

You can define your own permission in Drupal 7 by implementing hook_permission() in the module’s .module file. Here is an example:

function first_module_permission() {
	return array(
		'access first' => array(
			'title' => t('Access to first module content'),
			'description' => t('Allow users access to first module content'),
		)
	);
	
}

Once this is defined, if you head to the permission page in the admin section, you will see access first listed and you will be able to grant access to it to particular roles.

In your module’s implementation of hook_menu, change access arguments to reference the access first permission defined above.

function first_module_menu() {
  $items['first/custom'] = array(
	'title' => 'Custom page',
	'page callback' => 'first_module_custom', 
	'page arguments' => array(1),	  
	'access arguments' => array('access first'),
  );

  return $items;
}

Defining your own permission in Drupal 8

To define your own permission in Drupal 8, create a new file called: modulename.permissions.yml.

And in that file, define your permission like this:

access first:
  title: 'Access to first module content'
  description: 'Allow users access to first module content'
  restrict access: TRUE

To use this permission for a particular path, change the _requirements in the route .yml file to reference the access first permission:

first_module.content:
  path: '/first/{name}'
  defaults:
    _controller: 'Drupal\first_module\Controller\FirstController::content'
    _title: 'Hello world'
    name: 'Bob'
  requirements:
    _permission: 'access first'

Outro

Even though the way you define routes, menu items and permissions in Drupal 8 is totally different to Drupal 7, the overall concept is the same. Define a path, map it to a callback/controller and restrict access to a particular permission.