Route parameters and access
A route parameter is the changing part of an address: the year in /blog/year/2026 or the node number in /blog/5/info. One route with a parameter replaces any number of similar pages. The same route description says who may open the page.
A parameter in the path
A parameter is written in curly braces. Add a route to blog.routing.yml:
# web/modules/custom/blog/blog.routing.yml
blog.year:
path: '/blog/year/{year}'
defaults:
_controller: '\Drupal\blog\Controller\BlogController::year'
_title: 'Archive'
requirements:
_permission: 'access content'The value arrives in the method argument with the same name:
// web/modules/custom/blog/src/Controller/BlogController.php
public function year(string $year): array
{
return [
'#markup' => $this->t('Articles from @year.', ['@year' => $year]),
];
}After drush cr the address /blog/year/2026 shows "Articles from 2026.". The argument name must match the name in the braces: {year} and $year.
Validating a parameter
Right now /blog/year/abc matches too. To make the route work only for four digits, add a regular expression under the parameter name in requirements:
# web/modules/custom/blog/blog.routing.yml
requirements:
_permission: 'access content'
year: '\d{4}'Now /blog/year/abc returns a 404 error and the controller method is not called.
A default value
If defaults holds a value for the parameter, it can be left out of the address:
# web/modules/custom/blog/blog.routing.yml
defaults:
_controller: '\Drupal\blog\Controller\BlogController::year'
_title: 'Archive'
year: '2026'The address /blog/year opens the archive for 2026.
A node in a parameter
If a parameter is named after an entity type, for example node, Drupal loads the node by the number from the address itself:
# web/modules/custom/blog/blog.routing.yml
blog.info:
path: '/blog/{node}/info'
defaults:
_controller: '\Drupal\blog\Controller\BlogController::info'
_title: 'Article details'
requirements:
_entity_access: 'node.view'// web/modules/custom/blog/src/Controller/BlogController.php
use Drupal\node\NodeInterface;
public function info(NodeInterface $node): array
{
return ['#markup' => $node->label()];
}The address /blog/1/info shows the title of node number 1. If there is no such node, Drupal returns 404. The _entity_access: 'node.view' condition lets in only those who are allowed to see this node.
A link with a parameter
// in any PHP class
use Drupal\Core\Url;
$url = Url::fromRoute('blog.year', ['year' => 2025])->toString(); // /blog/year/2025Parameter values are passed as the second argument. In Twig it is path('blog.year', {'year': 2025}).
Access conditions
Entry in requirements | Who can open the page |
|---|---|
_permission: 'access content' | Those who have this permission |
_role: 'administrator' | Users with the role |
_user_is_logged_in: 'TRUE' | Logged-in users only |
_entity_access: 'node.update' | Those who may edit the node from the address |
_access: 'TRUE' | Everyone, without a check |
Your own permissions are declared in the blog.permissions.yml file — a separate lesson covers that.
Common mistake
Drupal treats a route without an access condition as closed: the page answers "Access denied" even to the administrator. And the _access: 'TRUE' entry used to "fix" it opens the page to everyone — including an internal one. Name a specific permission through _permission, and keep open access only for truly public pages.