Roles & Capabilities

Roles are named bundles of capabilities; capabilities are what WordPress actually checks. How to add and change both.

Syntaxadd_role( string $role, string $display_name, array $capabilities = array() ): WP_Role|null

A capability is a single string such as edit_posts, upload_files or manage_options. It is the only thing WordPress tests when deciding whether an action is allowed. A role is a named bundle of capabilities, and exists mainly so there is something to put in a dropdown.

The defaults

A single site ships five roles: administrator, editor, author, contributor and subscriber. Multisite adds a sixth, super admin, which is not a per-site role at all — it is a network-level list of users who pass almost every capability check on every site.

Where they live

Every role and its capability map sits in one serialised option, {$table_prefix}user_roles. A user's own grants live in the {$table_prefix}capabilities user meta key as an array of capability => bool, which is why a single user can hold a capability no role granted, and why role data is per-site on multisite.

Changing them

add_role( $role, $display_name, $capabilities ) returns a WP_Role, or null if the role already exists. It is shorthand for wp_roles()->add_role( ... ). Likewise get_role( 'editor' )->add_cap( $cap ) and wp_roles()->add_cap( 'editor', $cap ) are the same write. The capabilities argument must be a map, array( 'read' => true ); a plain list like array( 'read' ) stores a capability literally named 0. Capability names are arbitrary strings, so acme_manage_orders is as real to WordPress as edit_posts.

Example

Example · php
<?php
// Bump this constant whenever the capability lists below change.
const ACME_ROLES_VERSION = '3';

function acme_register_roles(): void {
	// Capabilities are a MAP of name => bool. A plain list will not work.
	add_role( 'acme_shop_manager', 'Shop Manager', array(
		'read'                 => true,
		'upload_files'         => true,
		'edit_posts'           => true,
		'edit_published_posts'  => true,
		'acme_manage_orders'   => true, // your own capability; just a string
	) );

	// Give an existing role one extra capability. get_role() is WP_Role|null.
	$editor = get_role( 'editor' );
	if ( $editor instanceof WP_Role ) {
		$editor->add_cap( 'acme_manage_orders' ); // persists to wp_options
		// Longhand for exactly the same write:
		// wp_roles()->add_cap( 'editor', 'acme_manage_orders' );
	}

	update_option( 'acme_roles_version', ACME_ROLES_VERSION );
}
register_activation_hook( __FILE__, 'acme_register_roles' );

// Activation does not run on plugin update, so re-apply when the version moves.
add_action( 'init', static function (): void {
	if ( get_option( 'acme_roles_version' ) !== ACME_ROLES_VERSION ) {
		acme_register_roles();
	}
} );

When to use it

  • Add a role for a client's staff who need media and their own posts but not plugins.
  • Give editors one custom capability that gates a plugin's admin screen.
  • Audit which roles hold a dangerous capability such as unfiltered_html.
  • Remove a role cleanly on uninstall without stranding the users on it.

More examples

Cleaning up on uninstall

Roles and capabilities are database state, so deleting the plugin leaves them behind unless you remove them. Moving the users first matters: a user whose only role is deleted keeps an empty capabilities array and loses access to everything, including their own profile.

Example · php
<?php
// uninstall.php — runs when the plugin is deleted, not when deactivated.
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
	exit;
}

// Take the capability back off every role that has it, including ones
// a site owner may have added it to by hand.
foreach ( wp_roles()->role_objects as $role ) {
	$role->remove_cap( 'acme_manage_orders' );
}

// Users left on this role fall back to no role at all, so move them first.
foreach ( get_users( array( 'role' => 'acme_shop_manager' ) ) as $user ) {
	$user->set_role( 'subscriber' );
}

remove_role( 'acme_shop_manager' );
delete_option( 'acme_roles_version' );

Granting a capability to one user, not a role

Use per-user capabilities for one-off exceptions — a contractor who needs a single extra permission — instead of inventing a near-duplicate role. Note the difference between add_role() and set_role() on WP_User: one appends, the other replaces.

Example · php
$user = get_user_by( 'email', '[email protected]' );

// WP_User::add_cap() writes into that user's own capabilities meta,
// leaving every other holder of their role untouched.
$user->add_cap( 'acme_manage_orders' );

// A user can also hold several roles at once.
$user->add_role( 'editor' );      // added alongside whatever they had
// $user->set_role( 'editor' );   // replaces all existing roles

// And a capability can be explicitly denied, overriding the role grant.
$user->add_cap( 'delete_published_posts', false );

Reading what a role actually holds

Useful while debugging a permission problem or building an admin screen that lists roles. $user->allcaps is the merged picture — role grants plus per-user grants and denials — and is what a capability check ultimately reads.

Example · php
$role = get_role( 'contributor' );

// WP_Role::$capabilities is the raw map: capability name => bool.
print_r( array_keys( array_filter( $role->capabilities ) ) );
// => edit_posts, delete_posts, read

// Every registered role, keyed by slug, with display names.
$all = wp_roles()->get_names();
// => array( 'administrator' => 'Administrator', 'editor' => 'Editor', ... )

// Roles a given user holds (slugs), and everything they can do.
$user = wp_get_current_user();
$slugs = $user->roles;    // e.g. array( 'editor' )
$caps  = $user->allcaps;  // role caps merged with per-user caps

Discussion

  • Be the first to comment on this lesson.