Drupal: Users, Roles and Permissions

This content was generated with the assistance of AI and should be verified against the official Drupal documentation before being relied on in production.

This sub-section’s Drupal bibliography lists the reference material consulted while preparing these pages.

Every request to a Drupal site is made by a user, and everything that user may do is decided by permissions that are granted to roles. This page explains the whole chain from the site builder’s point of view: the user account entity, the People screens and registration settings, roles and the permissions screen, the Administrator role, the special status of user 1 and the switch that removes it, the Access Policy API that computes permissions, the node access (grants) system and its "Rebuild permissions" step, the 11.3 permission that controls the published status of content, password policy, the CSRF-protected logout route and the four account cancellation methods. Writing permission checks in module code is covered in Access control and permissions in code; hardening a whole site is covered in Security hardening.

It was written against Drupal 11.4.x, with Drush 13 or 14 and PHP 8.3 or later, and the facts were checked against the official documentation on 2026-10-30. The running example is the Meridian Museum: the roles Administrator, Content Editor, Curator, Reviewer and Member, the content types exhibition and event, and the custom entity collection_object of the module meridian_collection. Drupal 12.0.0 is scheduled for the week of 2026-12-07 and the end of life of Drupal 10 for 2026-12-09, so both are described as upcoming.

Who uses this and how the pieces fit

Four kinds of people meet the access system:

  • editors see only what their roles allow, and notice it when a menu item or a button is missing;

  • site builders create roles, tick permissions, configure registration and decide how content is protected;

  • administrators create accounts, block or cancel them, and protect user 1;

  • developers declare permissions, write access policies and node grants (see Access control and permissions in code).

The User Guide (chapter 7, "Managing User Accounts") builds its model on three concepts: users, permissions and roles. Every visitor is a user. Visitors who are not logged in are anonymous users, logged-in visitors are authenticated users, and the account created during installation is user 1. A permission names one action or a small group of related actions, such as "View published content" or "Administer users", and is declared by the module that provides the action. You never give permissions to a person directly: permissions go to roles, and roles go to people. Every account automatically has the Authenticated user role, and any number of further roles can be added.

flowchart LR A[Request] --> U[Current user: anonymous or account] U --> R[Roles: anonymous OR authenticated + assigned roles] R --> P[Access policies compute permissions<br/>user.permissions cache context] P --> C{Permission check} C -->|route, form, field, block| X[Allowed or denied] C -->|content operation| N[Node access: bypass, module hooks, grants] N --> X S[Super user policy: user 1] -.->|all permissions| P AR[Administrator role is_admin] -.->|all permissions| P

The permissions of the current user are not read from the roles at the moment of each check. Since Drupal 10.3 they are calculated by access policies (described below), cached under the user.permissions cache context, and then looked up by $account->hasPermission(). For a site builder the visible part is still the role and permission screens.

User accounts

A user account is a content entity of type user (class Drupal\user\Entity\User). Its base fields include uid, uuid, langcode, preferred_langcode, preferred_admin_langcode, name (the username), pass (the password hash), mail, timezone, status (active or blocked), created, changed, access (last access), login (last login), init (the email address used at registration), roles and default_langcode. The user picture is a normal field added by the Standard profile, and you can add your own fields at Configuration > People > Account settings > Manage fields (admin/config/people/accounts/fields), exactly as you would for a content type (see Content types and fields). The Meridian site could, for instance, add a field_membership_number text field for members.

The User Guide describes creating an account at Manage > People (admin/people) with the Add user button (section 7.4, "Creating a User Account"). The form asks for:

  • Email address: used for system emails and not shown publicly;

  • Username: used to sign in and to author content; spaces are allowed, and the only punctuation accepted is periods, hyphens, apostrophes and underscores;

  • Password and Confirm password: with a strength gauge;

  • Status: Active or Blocked, and blocked users cannot sign in;

  • Roles: the additional roles for the account;

  • Notify user of new account: sends the "Welcome (new user created by administrator)" email;

  • Picture and Contact settings: the personal contact form switch, which only has an effect when the Contact module is installed.

The People page (admin/people) is a View (views.view.user_admin_people). It filters by name or email, role, permission and status, and offers bulk actions to add or remove a role, block or unblock, and cancel the selected accounts. Because it is a View, a site builder can add columns or filters to it like to any other listing (see Views).

Accounts can be managed from the command line with Drush, which is convenient in provisioning scripts and in the DDEV environment (see Drush). Passwords here come from the shell, never from a file in the repository.

# Create a curator account; the password is read from an environment variable
ddev drush user:create amara.okafor \
  --mail="amara.okafor@meridian-museum.example" \
  --password="$MERIDIAN_CURATOR_PASSWORD"
ddev drush user:role:add curator amara.okafor

# One-time login link for an administrator who is locked out
ddev drush user:login --name=amara.okafor

# Information, blocking, unblocking and a role listing
ddev drush user:information amara.okafor
ddev drush user:block amara.okafor
ddev drush user:unblock amara.okafor
ddev drush role:list --format=yaml

Registration settings

Configuration > People > Account settings (admin/config/people/accounts, permission Administer account settings) holds the settings stored in user.settings. The ones a site builder meets first are the registration options under Who can register accounts?:

Option Configuration value (register) Meaning

Administrators only

admin_only

Visitors cannot create accounts; staff create them at admin/people/create.

Visitors

visitors

Anyone can register, and the account is active immediately (after email verification when it is required).

Visitors, but administrator approval is required

visitors_admin_approval

Anyone can register, but the account stays blocked until an administrator activates it, and the visitor is told so.

Next to it you find Require email verification when a visitor creates an account (verify_mail), the default cancellation method (cancel_method, see below), and the editable email templates: "Welcome (new user created by administrator)", "Welcome (awaiting approval)", "Welcome (no approval required)", "Account activation", "Account blocked", "Account cancellation confirmation", "Account canceled" and "Password recovery". The [site:...] and [user:...] tokens are available in them. The same screen lets you set the Anonymous users name and the picture settings.

Default since 11.1. In new sites built with the Standard or Minimal installation profile, the default of Who can register accounts? is Administrators only. Earlier releases defaulted to visitors with administrator approval, which on public sites produced floods of spam accounts. The change record applies only to new sites: a site installed before Drupal 11.1 keeps whatever it has stored, so check the setting of every older site. Tutorials and books that tell you to "leave the default registration alone" for a members' area predate this change; on a fresh site you must change the option yourself before visitors can sign up.

For the Meridian Museum the choice is deliberate. Staff accounts are created by an administrator; Members (logged-in visitors who get a membership card page) register themselves with email verification, so the site builder sets the option to Visitors, keeps Require email verification on, and relies on a captcha or anti-spam module (see Forms for visitors: Contact and Webform) rather than approving each account by hand. The settings can be exported like any other configuration:

# config/sync/user.settings.yml (excerpt)
register: visitors
verify_mail: true
cancel_method: user_cancel_block
notify:
  status_activated: true
  status_blocked: false
  status_canceled: false
password_reset_timeout: 86400

Common pitfalls with registration:

  • Opening registration and giving the Authenticated user role permissions that were meant for staff. Everything an authenticated user can do, every registered spammer can do.

  • Turning off email verification on a public site, which lets anyone create accounts with other people’s addresses.

  • Forgetting that password reset links are valid for password_reset_timeout seconds (one day by default) and that the one-time login link produced by drush user:login follows the same rule.

  • Expecting the registration option to affect accounts created by JSON:API, migrations or code: those paths use the entity API directly and are protected by permissions, not by this setting.

Roles

A role is a configuration entity of type user_role (class Drupal\user\Entity\Role) exported as user.role.<id>.yml. Its properties are id, label, weight (the order in listings), is_admin and permissions. Two roles always exist: Anonymous user (anonymous) and Authenticated user (authenticated). They cannot be deleted or assigned to accounts by hand. A request is made either with the anonymous role or with the authenticated role plus every role assigned to the account. The Standard profile also creates Content editor and Administrator.

Roles are created at People > Roles (admin/people/roles) with Add role, and require the Administer permissions permission (section 7.3 of the User Guide, "Creating a Role"). The machine name is generated from the label and cannot be changed afterwards. Roles are reordered by drag and drop; the weight only affects listings and the order of the columns in the permissions screen, not what the role may do.

The Meridian roles, exported from the configuration screen, look like this:

# config/sync/user.role.curator.yml
langcode: en
status: true
dependencies:
  config:
    - node.type.exhibition
  module:
    - meridian_collection
    - node
id: curator
label: Curator
weight: 4
is_admin: false
permissions:
  - 'access content'
  - 'access content overview'
  - 'create exhibition content'
  - 'edit any exhibition content'
  - 'edit own event content'
  - 'view own unpublished content'
  - 'administer node published status'
  - 'manage collection objects'

Notice the dependencies block. Permissions create configuration dependencies: a role that grants create exhibition content depends on the module that declares the permission and on the configuration that the permission is generated from (here the content type). If a module is uninstalled or a content type is deleted, Drupal removes the permission from the role instead of leaving an orphan. When you export and import configuration between environments, the dependency order makes sure the content type exists before the role refers to it.

Assigning roles. A role is given to one account at its edit form, in the Roles section, or to many accounts at once from the People page by choosing a bulk action such as "Add the Curator role to the selected user(s)" and pressing Apply to selected items (User Guide section 7.6, "Changing a User’s Roles"). The permission Administer permissions (and Administer users for editing other accounts) is needed for this; both are marked as having security implications. The role assignment takes effect on the next request of that user, because the permission cache context depends on the roles.

Permissions

Permissions are declared by modules in MODULE.permissions.yml (static ones) or by a callback listed there under permission_callbacks (dynamic ones, such as "Create exhibition content" for every content type). The screens are:

  • People > Permissions (admin/people/permissions): one grid with every permission, grouped by module, and one column per role. On a site with many modules, the per-module page (admin/people/permissions/module/node) and the per-role page (admin/people/permissions/curator) are easier to use.

  • People > Roles > Edit permissions for one role, which is the form used by User Guide section 7.5, "Assigning Permissions to a Role": tick the boxes and press Save permissions.

A permission can carry restrict access: true. The screen then prints "Warning: Give to trusted roles only; this permission has security implications." Examples are Administer permissions, Administer users, Administer modules and Bypass content access control. A role that holds a restricted permission is as trustworthy as the people who hold that role. Another warning, specific to input formats, deserves attention: the permission to use a text format with unrestricted HTML (see Text formats and CKEditor 5) is also a way to run JavaScript in other users' browsers.

Useful permissions for the Meridian site:

Permission Effect

access content

View published content; required by almost everything on the front end.

view own unpublished content

Lets the author of a draft see it without any wider right.

create exhibition content, edit any exhibition content, delete own event content

Per-bundle content permissions, generated for every content type.

administer nodes

Broad administration of content, including the status, authoring and promotion options. Treat as an administrator right.

bypass node access

Ignores all node access checks (hooks and grants).

administer node published status

Added in 11.3: changes the Published checkbox without the rest of administer nodes.

administer permissions, administer users

Restricted: can grant any permission, or edit any account.

administer account settings

Opens admin/config/people/accounts.

cancel account, select account cancellation method

Lets users cancel their own account, and choose how.

change own username

Lets users rename themselves; off by default for authenticated users.

view the administration theme, access toolbar

Admin interface access for staff roles.

Anonymous and authenticated, in practice. Because every account holds the Authenticated user role, a permission granted to it is granted to all accounts, including Members. The anonymous role is a separate set: a permission given to authenticated users is not given to anonymous visitors. The permissions grid therefore needs a sanity check after each module installation, and the Status report (admin/reports/status) as well as the Security Review contrib module can point at the riskiest grants. Recipes for common shapes are covered in Recipes.

Permissions are configuration, so they can also be granted without clicking. A recipe such as meridian_events can grant permissions to roles through config actions (see Recipes for the full format):

# recipes/meridian_events/recipe.yml (excerpt)
name: 'Meridian events'
type: 'Content type'
install:
  - node
  - user
config:
  import:
    node:
      - node.type.event
  actions:
    user.role.content_editor:
      grantPermissions:
        - 'create event content'
        - 'edit own event content'
        - 'delete own event content'
    user.role.curator:
      grantPermissions:
        - 'edit any event content'

Source: Drupal recipes

And from the command line, for ad hoc work or provisioning:

ddev drush role:create reviewer "Reviewer"
ddev drush role:perm:add reviewer "access content,view own unpublished content"
ddev drush role:perm:remove authenticated "change own username"
ddev drush role:list --filter=reviewer
ddev drush config:export -y   # then commit config/sync/user.role.*.yml

The Administrator role

The Administrator role (administrator) of the Standard profile is a role whose is_admin flag is true. A role with this flag has all permissions, including the ones that modules add later, and its permission list is not even shown on the permissions page. That is why a new module’s permissions never need to be granted to administrators.

The flag can be turned on or off for any role, in the role’s edit form ("This role is an administrator role"), or by the is_admin key in configuration. Two practical consequences follow. First, any account that gets such a role has full control of the site, so the right to assign roles (Administer permissions and Administer users) is itself equivalent to administrator access. Second, because administrator roles have no permission list, you cannot make an administrator "almost" an administrator: split a limited operations role off, with explicit permissions, and keep is_admin for the few people who really need it.

In Drupal 12 the same model continues; the move of the Toolbar module to contrib and the removal of Claro mean that the admin interface a role sees changes, but not the is_admin mechanism. Check the upgrade page of your release (Preparing for Drupal 12).

User 1 and the super-user access policy

The account created by the installer has user ID 1 (uid 1). The User Guide (section 7.2, "Concept: The User 1 Account") calls it the root account: it can do everything regardless of its roles, including editing all content and all accounts, changing configuration, installing modules and running updates. It cannot be deleted from the admin interface, and removing it from the database is strongly discouraged. The guide recommends that every administrator has a separate account with the Administrator role and that user 1 is used as little as possible: separate accounts make actions traceable, allow restrictions per person, and make clear who authored what.

Drupal 10.3 and later: the super-user policy. Before 10.3 user 1 was hard-coded as an exception inside the access checks. Since the Access Policy API was added in 10.3, user 1’s privileges are provided by one core policy, the SuperUserAccessPolicy, which can be switched off. The switch is a container parameter in the site’s services file (sites/default/services.yml, copied from default.services.yml):

# web/sites/default/services.yml
parameters:
  # Treat user 1 like any other account: it only has what its roles grant.
  security.enable_super_user: false

Points to know before using it:

  • The container must be rebuilt after the change (clear caches from the UI, with drush cache:rebuild or with core/rebuild.php).

  • The change record recommends turning the policy off whenever the site has at least one other administrator. A stolen or guessed user 1 login is then no longer automatically a full compromise, and user 1 becomes an ordinary account.

  • The parameter has an effect only when user 1 does not also hold the Administrator role (a role with is_admin). An account that has both continues to be an administrator through the role.

  • Lockout recovery: set the parameter back to true and rebuild the container, and user 1 is a super user again.

  • Core’s own Browser, Kernel and Nightwatch tests run with the policy off. Tests of contributed modules keep it on unless the test class sets $usesSuperUserAccessPolicy to FALSE; test users must be given the permissions they need.

  • The change record states that the long-term aim is to remove the super-user policy entirely.

A sensible hardening recipe for Meridian: create named administrator accounts with the Administrator role, put the user 1 credentials in the password manager, set security.enable_super_user: false in the production services file (and keep it true locally if that is convenient), and use drush user:login from the command line, which can print a one-time login link for any account, when you are locked out.

Bad advice that older books and blog posts still contain: "log in as user 1 for everything", "give the editor the user 1 account", and "uid 1 bypasses everything, so test with it". Test with a real role instead, and remember that user 1 also bypasses node access, field access and routes with _permission requirements, so a feature that works for user 1 may fail for everybody else.

The Access Policy API from a site builder’s view

Since Drupal 10.3, permissions are computed by access policies. A policy is a service tagged access_policy that, for a given account and scope, returns a set of permissions. The calculation happens once per account and is cached with cache contexts, so that $account->hasPermission() is a cheap lookup. The documentation describes the idea as granting or revoking permissions based on an environmental variable such as the logged-in user, the time of day or the active URL.

Core ships these policies:

  • UserRolesAccessPolicy: reads the roles of the account and adds the permissions from every role. For roles flagged is_admin it grants admin access (all permissions). This is the policy behind everything in the previous sections.

  • SuperUserAccessPolicy: gives user 1 admin access unless security.enable_super_user is false.

What a site builder needs to know:

  • The role-and-permission screens are still where you work; policies do not replace them.

  • Contributed modules may add policies. A module that says "gives curators extra rights during a loan season" or "restricts editors to one domain" is probably doing it through a policy. Such permissions may not appear in the permission grid of a role, because they are computed, not stored. If someone has a permission that no role grants, look for a policy in *.services.yml (a service tagged access_policy) or in the module’s documentation.

  • Scopes: policies work within a scope and an identifier. The default scope is drupal, which core uses. Contributed modules can define other scopes, for example one per group or per domain. Core does not use other scopes.

  • The calculated permissions are immutable for the rest of the request: code cannot add a permission in the middle of a request. The API has a build phase (calculatePermissions()) and an alter phase (alterPermissions()).

  • Cache correctness is the responsibility of the policy: whatever the policy depends on must be declared through cache contexts (getPersistentCacheContexts()) or cacheable metadata, or users could see stale permissions.

For the Meridian Museum the Curator extra right is not a role right but an access policy, shown here from the module side so the site builder can recognise it. During the annual loan season (a flag in meridian_collection.settings) curators additionally get the permission manage object loans:

<?php

declare(strict_types=1);

namespace Drupal\meridian_collection\Access;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Session\AccessPolicyBase;
use Drupal\Core\Session\CalculatedPermissionsItem;
use Drupal\Core\Session\RefinableCalculatedPermissionsInterface;
use Drupal\Core\Session\AccountInterface;

/**
 * Grants curators the loan-management permission during the loan season.
 */
final class LoanSeasonAccessPolicy extends AccessPolicyBase {

  public function __construct(private readonly ConfigFactoryInterface $configFactory) {}

  public function calculatePermissions(AccountInterface $account, string $scope): RefinableCalculatedPermissionsInterface {
    $calculated = parent::calculatePermissions($account, $scope);
    // The result depends on the roles of the account and on the setting.
    $calculated->addCacheContexts(['user.roles']);
    $settings = $this->configFactory->get('meridian_collection.settings');
    $calculated->addCacheableDependency($settings);

    if (!in_array('curator', $account->getRoles(), TRUE) || !$settings->get('loan_season')) {
      return $calculated;
    }
    return $calculated->addItem(new CalculatedPermissionsItem(['manage object loans']));
  }

  public function getPersistentCacheContexts(): array {
    return ['user.roles'];
  }

}
# meridian_collection.services.yml
services:
  meridian_collection.loan_season_access_policy:
    class: Drupal\meridian_collection\Access\LoanSeasonAccessPolicy
    arguments: ['@config.factory']
    tags:
      - { name: access_policy }

The documentation advises marking policy classes final and not extending another module’s policy, because policies take part in both the build and the alter phase; decorate a policy instead. Keep the condition in a dedicated service if both a cache context and a policy need it. The policy above declares manage object loans in the module’s meridian_collection.permissions.yml, like any other permission, so it also appears in documentation and can be granted through roles when the loan season is not the criterion.

Common errors: forgetting the cache context (permissions then stay as computed for the first user of a cache entry); using \Drupal::currentUser() instead of the account passed to the method; and writing a policy for something that a role permission already expresses.

Node access and "Rebuild permissions"

Permissions such as edit any exhibition content are checked first, but content has a second, finer layer: the node access system. It decides who may view, update or delete an individual node.

For a node operation Drupal asks, in this order of importance:

  1. Is the user allowed by bypass node access, or is it an administrator? Then yes.

  2. Does a module’s hook_node_access() (or a node access control handler decision) say yes or no?

  3. If node grants are in use, does a grant in the node_access table match the grants the user holds?

  4. Otherwise, the role permissions for the bundle (edit own exhibition content, …​) and the published status apply.

The database part works as follows. A module implementing hook_node_access_records() writes records when a node is saved: a record has a realm (a namespace chosen by the module), a gid (a grant ID, an integer meaning whatever the module wants) and the flags grant_view, grant_update and grant_delete. On access the module’s hook_node_grants() tells core which gid values in each realm the user holds, for the operation being checked. If a record and a held grant match, access is given. Denials are implicit: a deny-all record is not stored. Core writes one default record with node ID 0 and realm all when no module uses grants. The node access system does not distinguish published from unpublished nodes by itself, so a module that cares has to use its realms to restrict access to unpublished content.

Listings are also affected: queries tagged node_access (Views, entity queries with accessCheck(TRUE), the search index) join the node_access table so users do not see rows they may not open. That is why entity queries must always call accessCheck() explicitly.

Records are written when a node is saved. If a module that provides grants is installed, changed, or has its rules altered, the existing nodes still carry the old records. Then Drupal shows "The content access permissions need to be rebuilt." The fix is Reports > Status report > Rebuild permissions (admin/reports/status/rebuild), which runs a batch over all nodes and calls the hooks again, or in code and scripts:

ddev drush php:eval '\Drupal::service(\Drupal\node\NodeAccessRebuild::class)->rebuild();'
ddev drush cache:rebuild

What to remember about the rebuild:

  • It can take a long time on big sites and rewrites the node_access table, so run it during a quiet period, and not on every deployment: only after the access rules changed.

  • The rebuild erases the node ID 0 ("view all") records, so a module that relies on such a record has to restore it after a rebuild, as the API documentation of hook_node_grants() warns.

  • A site with no node-access module uses only role permissions; the rebuild is then trivial. Modules such as Content Access, Group, Domain Access or Workspaces-related setups bring grants.

  • Content moderation does not use grants for its states; it uses permissions such as "Use the Draft to Review transition", see Content moderation, workflows and revisions.

In the Meridian example, curators from a partner museum may only see the objects marked for their loan program. That is implemented by a small module with grants in a realm meridian_loan; after enabling it, the first action of the site builder is Rebuild permissions.

The 11.3 "Administer node published status" permission

Until 11.3 the only way to let a person publish and unpublish content was administer nodes, which also gives access to authoring information, promotion settings, the revision log and many other options. Drupal 11.3 adds the permission administer node published status (shown as Administer node published status) so that editors can change the Published state of a node of any content type without the broad permission. The access check for the status field accepts either permission. administer nodes remains and still includes the capability.

For the Meridian Museum this separates two jobs: Content Editors write and save drafts, while Curators and Reviewers publish. Grant the new permission to the roles that publish, and remove administer nodes from them:

ddev drush role:perm:add curator "administer node published status"
ddev drush role:perm:remove curator "administer nodes"
ddev drush cr

Notes and pitfalls:

  • The permission governs the published checkbox of the node edit form. When Content Moderation controls a content type the Published checkbox is replaced by moderation states, and the transition permissions apply instead.

  • Contributed permission modules that offered an equivalent "administer node status" permission are being updated to use the core permission (one such module plans an update function that swaps its permission for the core one).

  • Roles that depended on administer nodes just to publish should be reviewed after upgrading to 11.3, then the broader permission can be removed from them.

Password policy

Core enforces a very small password rule set: the password must be non-empty and the form shows a strength gauge, which is advice only. Hashing uses PHP’s password_hash() through the password service, and stored hashes are upgraded when the user next logs in; Drupal 12 defaults to the argon2id algorithm (which requires PHP with argon2 support).

Organisations that need complexity, history or expiry rules use the contributed Password Policy module (drupal/password_policy, https://www.drupal.org/project/password_policy). Its constraints cover character types, delay, digits, history, length, letters, punctuation, upper and lower case and not using the username, and policies can force expiry and a reset for chosen roles. At the time of writing the stable 4.0.x release (4.0.3) declares compatibility with Drupal 9.1, 10 and 11, and the project is marked as minimally maintained, so check the release list for your Drupal version before relying on it, and check the Drupal 12 status page when planning that upgrade. Its limitation matters for audits: policies apply to passwords set through user forms in the web interface, so Drush, web services or migrations can bypass them.

Combine policy rules with multi-factor authentication or single sign-on where the project requires it (see Authentication and OAuth), and with flood control, which core has: the user.flood configuration limits failed logins per IP address and per user (by default five failed attempts per user within six hours and fifty per IP address per hour).

Logout and CSRF protection (10.3)

Before Drupal 10.3 anyone could log a visitor out by embedding /user/logout in an image or a link on another site. Since 10.3 the route user.logout requires a CSRF token. A request without a valid token is redirected to a confirmation form (route user.logout.confirm) that asks the user whether to log out, so an attacker’s link cannot silently end a session.

The change breaks hard-coded links. In templates, menus and blocks always generate the URL from the route so that the token is added:

{# Wrong: shows the confirmation form to every visitor #}
<a href="/user/logout">{{ 'Log out'|t }}</a>

{# Right #}
<a href="{{ path('user.logout') }}">{{ 'Log out'|t }}</a>

Practical notes: the standard User account menu already contains the correct link. A custom menu link that points to /user/logout as an external path (a common habit in older tutorials and books written for Drupal 8 and 9) has to be replaced by a link to the route user.logout. Decoupled front ends that call the logout path directly, as in the Next.js kiosk of the Meridian Museum, need the token from the logout_token returned at login, or they should use their token-based authentication flow (see Authentication and OAuth).

Account cancellation methods

An account can be cancelled by an administrator (from the People page, the account’s Cancel account tab or drush user:cancel) or by the owner, if the role has the cancel account permission. The form offers four methods, whose labels and machine names are:

Label Method What happens

Disable the account and keep its content.

user_cancel_block

The account is blocked; its content stays published under its name. This is the default.

Disable the account and unpublish its content.

user_cancel_block_unpublish

The account is blocked and its nodes and comments are unpublished.

Delete the account and make its content belong to the Anonymous user.

user_cancel_reassign

The account is deleted and its content is reassigned to the anonymous user. This cannot be undone.

Delete the account and its content.

user_cancel_delete

The account and all of its content are deleted. This cannot be undone.

The default (cancel_method in user.settings) is chosen on the Account settings screen. The method choice appears for users who hold select account cancellation method; other users get the default. Removing accounts is done in a batch, so the process works for people with a lot of content. The options "Require email confirmation to cancel account" and the notification emails are also on the Account settings screen.

Developers can react to cancellations with hook_user_cancel() for the three non-deleting methods, but the hook is not invoked for user_cancel_delete: put cleanup of the delete case in hook_user_delete(). A method that defines an access check cannot be configured as the default method.

For the Meridian Museum the default is user_cancel_block: curators who leave keep their authorship on exhibitions, which is needed for the museum’s records. Members who ask for erasure under data protection rules get user_cancel_reassign, after the Privacy Officer has checked the content. Never use user_cancel_delete on staff accounts that authored published exhibitions without an editorial review, because it removes their content, too. Note that user 1 cannot be cancelled from the interface, only blocked.

ddev drush user:cancel --delete-content amara.okafor    # user_cancel_delete
ddev drush user:cancel amara.okafor                     # uses user_cancel_block by default

Common mistakes and a review checklist

  • Everything on the Authenticated role. Grant permissions to specific roles; keep Authenticated for the minimum, since it includes every self-registered account.

  • Sharing user 1. Use named accounts with the Administrator role and consider security.enable_super_user: false.

  • Granting administer permissions or administer users to non-administrators. These two are equivalent to full control.

  • Assuming the grid shows everything. Access policies and node grants can add access that is not visible there.

  • Forgetting to rebuild node access after enabling or changing a grants module.

  • Missing accessCheck() in entity queries written for listings, which then ignores node grants.

  • Hard-coded /user/logout links in menus and themes since 10.3.

  • Treating registration defaults as stable. Verify register on every site, and never rely on the 11.1 default for sites that were installed before it.

  • Exporting configuration without reviewing user.role.*.yml diffs. A permission change is a security change; review the exported file in the pull request, as described in Configuration management.

Where older books and tutorials are outdated

Older books such as Drupal 10 Masterclass (2023) and Drupal 10 Development Cookbook (2023) describe the user and role screens correctly in outline, but pre-date several changes described above. The Standard profile in 11.4 no longer creates the Article and Page content types, so permission examples such as "Article: Create new content" refer to content types that you must create first. Registration used to default to visitors with administrator approval and now defaults to administrators only for new sites (11.1). User 1 used to be hard-wired and cannot be switched off before 10.3, whereas it can now be switched off with security.enable_super_user. The /user/logout route is CSRF protected since 10.3, and the Access Policy API (10.3) is not covered by the books at all. The permission to publish content on its own (11.3) did not exist. Finally, these books show checks written with \Drupal::currentUser() inside classes, where current code injects the account or current_user service instead.