On this page
- Drupal version compatibility
- 1. Prerequisites
- Typical structure
- Naming conventions
- 2. Anatomy of a route
- Required properties
- Defaults: Output handlers (one required)
- Defaults: Metadata (optional)
- Requirements: Access & validation (optional)
- Options: System interaction (optional)
- 3. Annotated example
- 4. Passing arguments to your code
- Route alias
- Passing arguments to controllers
- Using parameters from the URL path
- Fixed parameters via defaults vs dynamic parameters via URL
- Fixed operation parameter
- Dynamic operation parameter
- Routing for Form
- 5. Next steps
Structure of routes
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.phpNaming conventions
- File name: Must be
{module_name}.routing.yml. - Case logic: Module names are
snake_case, while class names arePascalCase.
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: enableDynamic 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
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion