Structure of routes

Last updated on
29 March 2026

A route maps a URL path to a specific function or class. In Drupal, routes are defined in a your_module.routing.yml file located in your module's root directory.

Drupal version compatibility

  • Modern: 11.x, 10.x
  • Legacy: 9.x, 8.x (Standard routing remains the same, but excludes Drupal 11+ features like route aliases).

1. Prerequisites

Before defining routes, ensure your module follows the PSR-4 directory standard.

Typical structure

my_module/
├── my_module.info.yml
├── my_module.routing.yml    <-- Routes go here
└── src/
    ├── Controller/
    │   └── HelloController.php
    └── Form/
        └── MySettingsForm.php

Naming conventions

  • File name: Must be {module_name}.routing.yml.
  • Case logic: Module names are snake_case, while class names are PascalCase.

2. Anatomy of a route

Each route is identified by a machine name (e.g., my_module.route_name). Below are the properties available for each entry.

Required properties

Property Purpose Implementation note
path The URL to the route Must have a leading slash (e.g., /book). The first item cannot be an argument. For example, /{node} is invalid.
defaults Output handler Must contain at least one handler (see Output handlers table below).
requirements Access control Conditions that must be met to grant access. All conditions must be met.
methods HTTP verbs In addition to the URL, you can also match on the method of the incoming request. By default, only POST, GET, and HEAD are allowed. If you need other verbs, you must specify the list of verbs. For example, [GET, POST, DELETE].

Note: You can also define optional parameters at the end of your path. See "Optional Parameters" in Using parameters in routes.

Defaults: Output handlers (one required)

Property Purpose Implementation note
_controller Custom logic A PHP callable. Use \Namespace\Class::method or a service service_id:method. See also an Introductory example to Drupal routes and controllers, and Services and dependency injection in Drupal.
_form Form API A class implementing Drupal\Core\Form\FormInterface. See Introduction to Form API for an example.
_entity_view Render entity Format: entity_type.view_mode It will find an entity in the path and render it in the given view mode. For example, _entity_view: node.teaser will return the render array of the {node} in teaser mode.
_entity_list Entity collection Format: entity_type. Uses the EntityListController. Provides a list of entities. For example, _entity_list: entity_type returns the render array of the entities list.
_entity_form Provide an edit form for an entity. Entity form handlers are defined in the entity metadata (annotation). For example, _entity_form: node.default will display the default node form. "node" in node.default refers to the entity ID, and "default" refers to the form handler key.

Defaults: Metadata (optional)

Property Purpose Implementation note
_title Page title for the route Static string for the page title. It may differ from the menu link title.
_title_arguments Translation args Additional arguments passed to the t() function for the title.
_title_context Translation context Context information passed to t() for the title.
_title_callback Dynamic title A PHP callable (typically classname::method) returning an instance of \Drupal\Core\StringTranslation\TranslatableMarkup.

Requirements: Access & validation (optional)

Determines what conditions must be met to grant access to the route. All conditions must be met to gain access.

Property Purpose Implementation note
_permission Permissions A permission string. Use , for AND logic and + for OR logic. For example, _permission: 'access content'. Module-specific permission strings can be defined in my_module_name.permissions.yml.
_role Role check The machine name of one or more roles. Use , (AND) or + (OR) for machine-name roles. Permissions are preferred. Examples: _role: organizer,participant,controller,site_admin: Only users with all of these roles will be granted access to the route. _role: organizer+participant+controller+site_admin: Any users with at least one of these roles will be granted access to the route.
_access Global access Set to 'TRUE' (string/uppercase) to grant access to everyone.
_entity_access Entity security Checks access level. Format: [slug].[operation]. For example, node.view. Typically, the slug is an entity type ID, but it can be any slug defined in the route. The route match parameter corresponding to the slug is checked to see if it is entity-like and implements EntityInterface. You can also specify how the entity should be validated (node: \d+). This is useful if the used routes are /foo/{node} and /foo/bar, where {node} is a node ID. /foo/bar won't match /foo/{node} because bar isn't a numeric value.
_entity_create_access Create security Format: entity_type:{bundle_parameter} . For example, media:{media_type}).
_custom_access Custom logic See Custom route access checking
_format Request format Matches against the _format query parameter. For example, json.
_content_type_format Header check Matches against the Content-type HTTP header. For example, you can have _content_type_format: json and only match requests where the Content-type header is JSON.
_module_dependencies Dependency check Only allow route if modules are active. You can combine module names with a + (plus) for an AND relationship or , (comma) for an OR relationship. For example, _module_dependencies: 'node + search' means both node and search are needed, while _module_dependencies: 'node, search' means either node or search is needed. If your module already depends on other modules for its behavior (via .info.yml dependencies), there is no need to specify the dependency here as well. However, for optional dependencies, where routes are provided only if those optional dependent modules are also enabled, this is a useful option.
_user_is_logged_in Auth status Boolean. Requires the user to be authenticated or unauthenticated.
_access_user_register Registration check 'TRUE' grants access if registration is open AND the user is anonymous.
_csrf_token Action security Set to 'TRUE' for operations not using a form. See CSRF access checking for details.
_csrf_request_header_token REST security Requires an X-CSRF-Token HTTP header.

Options: System interaction (optional)

Property Purpose Implementation note
_admin_route Indicate whether this is an admin route or not, so the admin theme is used Set to TRUE to use the admin theme (default for /admin/*).
_auth The default authentication manager Limit mechanisms: _auth: ['basic_auth', 'cookie']. See Authentication API overview
_maintenance_access Show the page normally while in maintenance mode. Set to TRUE to bypass the site-offline message.
no_cache Caching Set to TRUE to mark the response as uncacheable.
parameters Parameter logic See Using parameters in routes

3. Annotated example

# Each route is defined by a machine name, in the form of my_module_name.route_name.

book.render:

# The path always starts with a leading forward-slash.

path: '/book'

# Defines the default properties of a route.

defaults:
# For page callbacks that return a render array use _controller.
_controller: '\Drupal\book\Controller\BookController::bookRender'

# Require a permission to access this route.

requirements:
_permission: 'access content'

book.export:

# This path takes dynamic arguments, which are enclosed in { }.

path: '/book/export/{type}/{node}'
defaults:
# This route returns a Response object so also uses _controller
_controller: '\Drupal\book\Controller\BookController::bookExport'
requirements:
_permission: 'access printer-friendly version'
# Ensure user has access to view the node passed in.
_entity_access: 'node.view'
options:
# Enable the admin theme for this route.
_admin_route: TRUE


4. Passing arguments to your code

Route alias

As of Drupal 11.x, a route can have an alias.

my_route.alias: alias: 'main_route' 

Passing arguments to controllers

All keys under the defaults section, which do not start with an underscore, will be passed in as arguments to the controller. Name your arguments appropriately for the arguments of the controller. For example a my_module_name.routing.yml file with the following:

example.content:
  path: '/example' 
  defaults: 
    _controller: '\Drupal\example\Controller\ExampleController::content' 
    custom_arg: 12
  requirements: 
    _permission: 'access content' 

Will pass on $custom_arg to the controller, so your content method can take $custom_arg:

// ...
public function content($custom_arg) {
// Now you can use $custom_arg (which will get 12 here).
}

Using parameters from the URL path

If your route defines parameters directly in the path (e.g., {op}), Drupal automatically passes these parameters as arguments to the controller method by matching their names.

Example:

entity.block.operation:
  path: '/admin/structure/block/manage/{block}/{op}'
  defaults:
    _controller: '\Drupal\block\Controller\BlockController::performOperation'
  requirements:
    _entity_access: 'block.{op}'
    op: 'enable|disable'

In this example, the controller method will receive both $block and $op parameters extracted from the URL:

public function performOperation($block, $op) {
  if ($op === 'enable') {
    // Enable block logic
  }
  elseif ($op === 'disable') {
    // Disable block logic
  }
}

Fixed parameters via defaults vs dynamic parameters via URL

Fixed operation parameter

When the operation is fixed and tied to a specific route (e.g., always enabling a block), you specify the parameter in defaults. The controller receives this fixed value without needing to parse the URL:

entity.block.enable:
  path: '/admin/structure/block/manage/{block}/enable'
  defaults:
    _controller: '\Drupal\block\Controller\BlockController::performOperation'
    op: enable

Dynamic operation parameter

When the operation is variable and part of the URL, you define it as a path parameter {op}. Drupal parses the URL and passes this value directly to the controller:

entity.block.operation:
  path: '/admin/structure/block/manage/{block}/{op}'
  defaults:
    _controller: '\Drupal\block\Controller\BlockController::performOperation'
  requirements:
    op: 'enable|disable'

Routing for Form

module_name.route_machine_name:
    path: '/{module_name}/personal-info'
    defaults:
        _form: 'Drupal\{module_name}\Form\InfoForm'
        _title: 'Personal information'
    requirements:    
        _permission : 'custom_module_permission'
Parameter Description
module_name The machine name of your module.
custom_module_permission The name of the permission implemented in module_name.permission.yml.

Combine this with dynamic routes to get true flexibility.


5. Next steps

After modifying your routing.yml file, you must clear the routing cache for Drupal to recognize the changes: bash drush cache:rebuild.

Help improve this page

Page status: No known problems

You can: