02.04.2026 | Lothar Ferreira Neumann

Updating user roles using a module update hook

switch_roles.install
<?php

use Drupal\user\RoleInterface;

/**
 * Remove the old roles and replace them with the new ones.
 */
function MY_MODULE_update_9041(): void {
  $em = \Drupal::entityTypeManager();
  $user_storage = $em->getStorage('user');

  // Ensure the new role exists. If not, create it first.
  $role_storage = $em->getStorage('user_role');
  if ($role_storage->load('new_role') === NULL) {
    /** @var \Drupal\user\RoleInterface $role */
    $role = $role_storage->create([
      'id' => 'new_role',
      'label' => 'New role',
    ]);
    $role->save();
  }

  $uids = \Drupal::entityQuery('user')
    ->accessCheck(FALSE)
    ->execute();

  if (empty($uids)) {
    return;
  }

  /** @var \Drupal\user\UserInterface[] $users */
  $users = $user_storage->loadMultiple($uids);

  foreach ($users as $user) {
    if ($user->hasRole('old_role')) {
      $user->addRole('new_role');
      $user->removeRole('old_role');
    }

    $user->save();
  }
}

Issue:

You need to migrate users from one role to another – for example, due to changes in permissions, content access strategy, or a cleanup of legacy roles. Manually updating each user is inefficient, especially on sites with many users.

Solution:

This update hook first checks whether the target role (new_role) exists. If it does not, the role is created programmatically.

It then fetches all user IDs using entityQuery with accessCheck(FALSE) to ensure all users are included. The user entities are loaded via user storage, and each user is checked for the presence of the legacy role (old_role). If found, the hook adds new_role, removes old_role, and saves the user entity.

This approach ensures consistency across the user base and allows role migration to be handled safely and repeatably as part of a structured deployment process.