min read
May 10, 2023
October 14, 2014

Porting access callbacks to Drupal 8

Porting access callbacks to Drupal 8
Table of contents

Menu system in Drupal 8 has been changed completely. One of the biggest changes being removal of hook_menu(). Now the menu items, its page callbacks/access callbacks... are all defined in yml files(*.routing.yml). There has been a shift in terminology as well from path -> routes.

What are access Callbacks?

Access callbacks are functions returning TRUE if the user has access rights to this menu item, and FALSE if not. Most of the access control can be achieved using Drupal permission system itself, but cases wherein we need to control the access to a menu link dynamically, access callbacks come into play.

e.g., Consider a case where in we need to allow the access to a menu only if it has a CSRF token attached with it.

How do we create access Callbacks in Drupal 7

The example below will validate for CSRF token in the url attached as ?token=<token_value>. If the token is valid, the requesting user will be granted access else not. 


 * Implements hook_menu()

function d7_demo_menu() {
  $items['d7-demo/access-check'] = array(
    'title' => t('Demo path for access check'),
    'page callback' => 'd7_demo_callback',
    'access callback' => d7_demo_csrf_token_check,
    'type' => MENU_LOCAL_TASK,
  return $items;

 * Access callback for demo path
function d7_demo_csrf_token_check() {
  return drupal_valid_token($_GET['token']);

How to port it to Drupal 8

  • Convert menu into routes
  • Create a service for custom access check
  • Create access callback class defined in the service.

Covert menu into routes

The menu definition goes completely into d8_demo.routing.yml as shown below.



  path: 'd8-demo/access-check'


    _controller: '\Drupal\d8_demo\Controller\d8DemoController::d8DemoRenderContent'


    _access_check_token: 'TRUE'

What is _access_check_token here?

We will take a look at it in the next step.

Details on the attributes used above:

pathDrupal 8 has replaced the use of path over route names. So, wherever in a module a path was being used, it has been replaced with route names. The index for the items array in hook_menu() moves under path attribute. Dynamic paths like d8-demo/% get replaced as d8-demo/{arg}. For entity paths like node/%nodewhich covert the entity id into the object before passing it to the page callback, it becomes node/{node}.

defaultsPage callbacks are converted into controllers now. These controllers could be of type _content, _controller, _form, _entity_view, _entity_list or _entity_form. For more details on which type of controller should be used where check the documentation here.

requirements: Determines what conditions must be met in order to grant access to the route. For more details, check the documentation here. We will be talking about_access here.

options (optional): Additional options on how the route should interact. For more details, check the documentation here.

Create a service for custom access check



    class: Drupal\d8_demo\Access\d8DemoCsrfCheck


      - {name: access_check, applies_to: _access_check_token}

    arguments: ['@csrf_token']

arguments: injects other services that this custom service requires. In our case, since we need to validate CSRF token in the path, we need to usecsrf_token service.

For details on rest of these attributes mean here, read my previous blog post on porting hook_init()

Adding access callback
The access callback class declared above in services.yml, needs to be implementAccessInterface.

namespace Drupal\d8_demo\Access;

use Drupal\Core\Routing\Access\AccessInterface;

use Drupal\Core\Session\AccountInterface;

use Symfony\Component\Routing\Route;

use Symfony\Component\HttpFoundation\Request;

use Drupal\Core\Access\CsrfTokenGenerator;


 * Determines access to routes based on login status of current user.


class d8DemoCsrfCheck implements AccessInterface {


   * Constructs a d8DemoCsrfCheck object.


   * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token

   *   The CSRF token generator.


  function __construct(CsrfTokenGenerator $csrf_token) {

    $this->csrfToken = $csrf_token;



   * {@inheritdoc}


  public function access(Route $route, Request $request, AccountInterface $account) {

    return $this->csrfToken->validate($request->query->get('token')) ? AccessInterface::ALLOW : AccessInterface::DENY;



AccessInterface requires us to define our logic into access(Route $route, Request $request, AccountInterface $account). In our case since we need to validate the csrf_token, we injected csrf_token service into our custom access check. Hence, its available in the constructor for the class. For those who are not familiar with the term dependency injection, watch this excellent video on how its done in Drupal8.


In this post, we touchbased on how to replace hook_menu() with a *.routing.yml file. We also learned on how to declare access callbacks in Drupal8 and then moving the code for access callback into access Controllers. 

Next article, we’ll have a look at How to port Blocks to Drupal 8.

Written by
No art workers.
We'd love to talk about your business objectives