A locked door only matters if someone actually checks the key before letting people through. In Drupal, that check happens at the route level — before your form's buildForm() ever runs, the routing system decides whether the current visitor is even allowed to reach it. This lesson covers the real mechanism: requirements in a route's .routing.yml entry, not anything defined inside the form class itself.
What you'll learn in this lesson
- Why form access is controlled at the route, not inside the form class
- The three real requirement keys:
_permission,_role, and_custom_access - How to define a custom permission and grant it to roles
Access is a routing.yml requirement, not a form method
The simplest and most common check — a single named permission — goes straight in the route definition:
my_module.secure_form:
path: '/secure-form'
defaults:
_form: '\Drupal\my_module\Form\SecureForm'
_title: 'Secure Form'
requirements:
_permission: 'access secure form'
And the form class itself stays completely ordinary — no access-related code inside it at all:
namespace Drupal\my_module\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class SecureForm extends FormBase {
public function getFormId() {
return 'secure_form';
}
public function buildForm(array $form, FormStateInterface $form_state) {
$form['message'] = [
'#markup' => $this->t('Welcome to the secure form!'),
];
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Submit'),
];
return $form;
}
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->messenger()->addMessage($this->t('Form submission successful!'));
}
}
access($access_checker, $user) method on FormBase — that pattern doesn't exist in Drupal's Form API. Access control for a form-based route lives in the route's requirements key, exactly like it does for a controller route. Also swapped drupal_set_message() — removed in Drupal 8 — for $this->messenger()->addMessage().Defining and granting the permission
A permission referenced in _permission must be declared in your module's *.permissions.yml file before it means anything:
access secure form:
title: 'Access Secure Form'
description: 'Allows users to access the secure form.'
restrict access: TRUE
restrict access: TRUE adds a warning icon next to this permission on the admin Permissions page — a signal to site builders that granting it carries real risk, worth using for anything beyond routine content-editing access. Once declared, an administrator assigns it to roles at People → Permissions, and Drupal checks it automatically on every request to that route — no code runs to re-check it.
Beyond a single permission
Two other requirement keys cover cases a single permission can't:
_role: 'administrator'— restrict by role name directly, rather than a permission a role happens to hold._custom_access: '\Drupal\my_module\Access\SecureFormAccessCheck::access'— a callback for logic a static permission check can't express, such as "only the entity's own owner, or an administrator."
Quick check: your form should be reachable by any authenticated user who owns the content it edits, but never by other authenticated users, regardless of their permissions. Which requirement key fits —
_permission,_role, or_custom_access? (Answer:_custom_access— "owns this specific content" is a per-request, per-entity decision that a static permission or role name can't express on its own; it needs a callback that inspects the current user and the specific entity being accessed.)
Key takeaways
- Form access is enforced by the routing system before
buildForm()runs — there's no access method to define inside the form class itself. _permissionhandles the common case: a single named permission, declared in*.permissions.ymland granted to roles via the admin UI.- Use
_roleto restrict by role name directly, and_custom_accessfor logic that depends on more than a static permission check (like entity ownership). restrict access: TRUEin a permission's definition flags it as sensitive in the admin UI — use it for anything beyond routine access.drupal_set_message()is dead Drupal 7 code — use the Messenger service instead.
Coming up next
Permissions gate who can reach a form at all — but a form that accepts file uploads has its own, narrower category of risk regardless of who's submitting it. Next, we'll look at securing file uploads specifically: validating extensions, size limits, and where uploaded files actually get stored.