Creating permissions in .permissions.ymlfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Think of permissions as the name tags at a members-only event: the door itself (a route) doesn't know who anyone is — it just checks whether the name tag says the right thing. In Drupal, that name tag system is .permissions.yml. Before any route, block, or menu link can be restricted to certain users, the permission it checks for has to actually exist somewhere the system recognizes — and that's exactly what this file declares.

What you'll learn in this lesson

  • Why permissions must be declared before they can be used anywhere else
  • The exact YAML shape a permission definition takes, key by key
  • How a declared permission becomes a real checkbox on the site's permissions page
  • The difference between a static permission (this lesson) and a dynamically generated one (next lesson)

The source file

Every module that defines its own permissions ships a MODULE_NAME.permissions.yml file in its root directory. Here's a real, working example from Drupal core's own Examples project (page_example.permissions.yml):

'access simple page':
  title: Access simple page
  description: Allow users to access simple page

'access arguments page':
  title: Access page with arguments
  description: Allow users to access page with arguments

How it works, key by key

The permission's machine name

'access simple page':

This top-level key — quoted because it contains spaces — is the permission's actual machine name. It's what you'll write in a route's _permission requirement, in a block's visibility settings, or in a call to $account->hasPermission(). Get this string wrong anywhere it's referenced and Drupal won't throw an error — it'll just silently deny access to everyone, since a permission that doesn't match anything defined here is treated as never granted.

title and description

title: Access simple page
description: Allow users to access simple page

title is the short label shown next to the checkbox on admin/people/permissions — this is what a site administrator actually reads when deciding whether to grant it to a role. description appears as smaller helper text right below it. Neither has any effect on the code path — they exist purely to help a human administrator understand what they're granting, so it's worth writing them as if a stranger will have to make a decision based on them alone, because that's exactly what happens.

A permission with no title or description will still work — Drupal falls back to just showing the machine name on the permissions page. It works, but it's a poor experience for whoever administers your site later, possibly not you.

See it for yourself

Visit admin/people/permissions on any Drupal site with the Examples module enabled, and search the page for "simple page" — you'll find the exact checkbox this YAML produced, grouped under the module's own section.

Quick check: you add a new permission key to this file, but forget to clear the cache. Will it show up on the permissions page? (Answer: no — permission definitions are collected and cached at cache-clear time, same as routes and services. A newly added permission is invisible to the system until drush cr or an equivalent cache rebuild runs.)

Key takeaways

  • A permission must be declared in MODULE_NAME.permissions.yml before it can be referenced anywhere else in your module.
  • The machine name (the quoted top-level key) is what your code and routing actually check — treat it as an identifier, not just a label.
  • title and description are purely for the human administrator reading the permissions page — write them clearly.
  • New permission definitions require a cache rebuild before they appear or take effect.

Coming up next

Declaring a fixed, known set of permissions like this works great when you know exactly what they are ahead of time. But what if the number of permissions your module needs depends on data — one permission per content type, say, or one per custom entity bundle? The next lesson, Defining Dynamic Permissions, covers exactly that: generating permission definitions programmatically with hook_permission() instead of hardcoding them in YAML.