Mastering WordPress Roles and Capabilities

Share this article

User management on WordPress is based on roles and capabilities.

A role is an entity made of a unique name and a set of capabilities. Each capability is used to define if the role has access to a particular feature of the platform.

Let’s take a closer look at how WordPress Roles and Capabilities work.

Under the Hood

Storing the Roles

The list of default roles and capabilities is available on the WordPress Codex.

The database stores this list in the wp_options table.

It uses the serialized wp_user_roles key.

wp_options table

The unserialized data looks like this:

array(
    'administrator' => array(
        'name'         => 'Administrator',
        'capabilities' => array(
            'switch_themes'          => true,
            'edit_themes'            => true,
            'activate_plugins'       => true,
            'edit_plugins'           => true,
            'edit_users'             => true,
            // [...]
        )
    ),
    'contributor' => array(
        'name'         => 'Contributor',
        'capabilities' => array(
            'delete_pages'           => true,
            'delete_others_pages'    => true,
            'delete_published_pages' => true,
            'delete_posts'           => true,
            // [...]
        )
    ),
    // [...]
);

This meta is automatically set when installing a new WordPress site.

When WordPress starts, the WP_Roles class loads the list from the database.

This occurs between the plugins_loaded and the init hooks.

Linking Users to Roles

WordPress uses a meta_key, stored in the wp_usermeta table, to link a user to his role.

wp_usermeta table

Once unserialized, the meta looks like this:

array(
    'administrator' => true
)

Note that WordPress uses an array, although a user can only have one role at a time, we will see later why.

Also, keep in mind that the wp_ part of the meta key is the prefix of the current blog.

(We can get it by using the $GLOBALS['wpdb']->get_blog_prefix() function).

On a multisite installation, this would allow a user to use different roles on different instances:

  • wp_capabilities => a:1:{s:13:"administrator";b:1;}
  • wp_10_capabilities => a:1:{s:11:"contributor";b:1;}
  • wp_15_capabilities => a:1:{s:10:"subscriber";b:1;}
  • [...]

This rule also applies to the wp_user_roles entry that we have seen before, in the wp_options table.

Finally, we can see the wp_user_level meta along with the role.

It was used to handle roles in old WordPress versions, and is now deprecated.

Working with Capabilities in the Core

We have seen how roles are loaded and linked to users; from there, WordPress is able to get the capabilities of a given user, when needed.

Several default capabilities are hard-coded in the WordPress core.

For instance, when loading the plugin screen, it will check if the current user can manage plugins, by running the following code:

if (!current_user_can('activate_plugins'))
{
    wp_die(__('You do not have sufficient permissions to manage plugins for this site.'));
}

Roles are never hard-coded; a role is only a capabilities wrapper, it only exists in the database.

Working with Roles & Capabilities: the WordPress API

Access the API

WordPress provides the following global functions to help us work with roles.

current_user_can()

Checks if the current user owns the required capability.

add_action('init', function()
{
    if (current_user_can('install_plugins'))
    {
        echo 'you can install plugins';
    }
    else
    {
        echo 'You cannot install plugins';
    }
});

WP_User::has_cap

Checks if a specific user owns a capability.

add_action('init', function()   
{
    $user = get_user_by('slug', 'admin');
    if ($user->has_cap('install_plugins'))
    {
        echo 'Admin can install plugins';
    }
    else
    {
        echo 'Admin cannot install plugins';
    }
});

We can note that current_user_can uses this function.

get_editable_roles()

Returns available roles.

add_action('admin_init', function()
{
    $roles = get_editable_roles();
    var_dump($roles);
});

The list may be overriden with the editable_roles filter, so we should not rely on this function to get the complete roles list on a website.

Note the usage of the admin_init hook, as the function is not loaded yet on the init one.

get_role()

Gets a WP_Role object from its slug.

add_action('init', function()
{
    $role = get_role('administrator');
    var_dump($role);
});

// This will print:
// WP_Role Object
// (
//     [name] => administrator
//     [capabilities] => Array
//         (
//             [switch_themes] => 1
//             [edit_themes] => 1
//             [activate_plugins] => 1
//             [edit_plugins] => 1
//             [...]

WP_Role::has_cap()

Checks if a role has the required capability.

add_action('init', function()
{
    $role = get_role('administrator');
    var_dump($role->has_cap('install_plugins')); // Prints TRUE
});

Customizing the API

WordPress also offers a complete API to customize the roles and their capabilities.

add_role()

Registers a new role in the database.

add_action('init', function()
{
    add_role('plugins_manager', 'Plugins Manager', array(
        'install_plugins',
        'activate_plugins',
        'edit_plugins'
    ));
});

remove_role()

Removes the required role from the database, if it exists.

add_action('init', function()
{
    remove_role('plugins_manager');
});

WP_Role::add_cap()

Adds a capability to a role.

add_action('init', function()
{
    $role = get_role('contributor');
    $role->add_cap('install_plugins');
});

This may be a core capability (install_plugins, edit_posts, …) or any custom string (my_awesome_plugin_cap).

It allows us to register as many custom capabilities as we need for our plugins.

WP_Role::remove_cap()

Removes a capability from a role, if it exists.

add_action('init', function()
{
    $role = get_role('contributor');
    $role->remove_cap('install_plugins');
});

WP_User::add_role()

Adds a role to the given user.

add_action('init', function()
{
    $user = get_user_by('slug', 'admin');
    $user->add_role('contributor');
});

This function allows you to theoretically set many roles on the same user.

As the WordPress backend only displays and manages one role per user, we should not add several roles for a user, and always use WP_User::remove_role() before adding a new one.

WP_User::remove_role()

Removes a role from the given user.

add_action('init', function()
{
    $user = get_user_by('slug', 'admin');
    $user->remove_role('administrator');
});

WP_User::add_cap()

Adds a capability to the given user.

add_action('init', function()
{
    $user = get_user_by('slug', 'admin');
    $user->add_cap('my_custom_cap');
});
wp_usermeta table with custom caps

This can be useful if we want to add a single capability to a user, without having to create a complete role.

WP_User::remove_cap()

Removes a capability from the given user.

add_action('init', function()
{
    $user = get_user_by('slug', 'admin');
    $user->remove_cap('my_custom_cap');
});

A Few Issues with the WordPress API

Everything looks fine with the functions we have seen, except one thing: database access and performance.

The main concern we have when working with roles and capabilities is when should we trigger our code?

To explain this, let’s have a look at the code of the WordPress core.

First, we want to add a new, empty role:

add_action('init', function()
{
    add_role('plugins_manager', 'Plugins Manager', array());
});

Here are the first lines of the add_role function (which actually redirects to WP_Roles::add_role):

public function add_role( $role, $display_name, $capabilities = array() ) {
        if ( isset( $this->roles[$role] ) )
            return;

If we add a new role, the add_role function runs once, and then does nothing.

Next, let’s say we want to add a capability to our freshly created role:

add_action('init', function()
{
    $role = get_role('plugins_manager');
    $role->add_cap('install_plugins');
});

The WP_Role::add_cap() function in WordPress 4.2.2 looks like this:

public function add_cap( $role, $cap, $grant = true ) {
    if ( ! isset( $this->roles[$role] ) )
        return;

    $this->roles[$role]['capabilities'][$cap] = $grant;
    if ( $this->use_db )
        update_option( $this->role_key, $this->roles );
}

It updates the $this->roles object, but we can also see that the database will be updated each time our code runs, even if our new capability has already been registered!

This means that if we care about performance, all the code we produce to customize roles and capabilities should not run on each page load.

Workarounds

There are several options to avoid these database problems.

Working with Plugin Activation

WordPress allows plugin authors to trigger code when a plugin is enabled from the backend, by using the register_activation_hook() function.

Let’s create a sample plugin:

/*
Plugin Name: Our sample role plugin
*/
register_activation_hook(__FILE__, function()
{
    $role = add_role('plugins_manager', 'Plugins Manager', array());
    $role->add_cap('install_plugins');
});

This code would run only once, when enabling the plugin on the website.

Now, we have to keep in mind that this solution depends on plugin activation and deactivation.

What would occur if the plugin is already in production, or if its reactivation is omitted when pushing the update?

In fact, this solution depends on the database too, and requires an extra step when pushing code.

Bypassing the WordPress Database

There is a second, undocumented solution, that can work well in some cases.

Let’s have a last look at the WordPress core, when the WP_Roles object loads the roles from the database on WordPress boot:

protected function _init() {
    global $wpdb, $wp_user_roles;
    $this->role_key = $wpdb->get_blog_prefix() . 'user_roles';
    if ( ! empty( $wp_user_roles ) ) {
        $this->roles = $wp_user_roles;
        $this->use_db = false;
    } else {
        $this->roles = get_option( $this->role_key );
    }

Before getting the data from the database, WordPress checks for the $wp_user_roles global variable.

If set, WordPress will use its content, and block database usage by setting the $this->use_db variable to false.

Let’s try this, by keeping only a new, restricted administrator role:

/*
Plugin Name: Our sample role plugin
*/
$GLOBALS['wp_user_roles'] = array(
    'administrator' => array(
        'name' => 'Administrator',
        'capabilities' => array(
            'activate_plugins' => true,
            'read' => true,
        )
    )
);

When loading the backend, we can see it has kept the definition of our custom role:

custom backend

This solution solves the database problem, but may introduce some others:

  • Plugins which use the native API may not behave correctly.
  • We have to manually set the definition of each role, even the ones we don’t want to change.

However, when building a custom WordPress application that needs a custom, static list of roles, this could be a possible solution:

  • The roles definition can be versioned with the code.
  • Pushing new code on an environment will automatically update the definition.
  • No more questions about the database.

Conclusion

In this article, I’ve presented an overview of roles and capabilities usage in WordPress.

Although its complete API allows us to do almost whatever we want, the relation to the database remains the main concern.

We’ll have to keep this in mind when developing our plugins and themes.

What do you think about the way WordPress manages roles? I look forward to your feedback!

Frequently Asked Questions on Mastering WordPress Roles and Capabilities

What are the default user roles in WordPress and what are their capabilities?

WordPress comes with six default user roles: Super Admin, Administrator, Editor, Author, Contributor, and Subscriber. Each role has a specific set of capabilities. For instance, a Super Admin has access to all administrative features across multiple sites. An Administrator can perform all administrative tasks on a single site. Editors can publish and manage posts, including those of other users. Authors can publish and manage their own posts. Contributors can write and manage their own posts but cannot publish them. Subscribers can only manage their profile.

How can I add a new user role in WordPress?

To add a new user role in WordPress, you can use the add_role() function. This function accepts three parameters: the role, the display name, and an array of capabilities. For example, to add a new role called ‘custom_role’ with the capability to read and edit posts, you would use the following code:

add_role(
'custom_role',
__( 'Custom Role' ),
array(
'read' => true, // Can read posts
'edit_posts' => true, // Can edit posts
)
);

How can I remove a user role in WordPress?

To remove a user role in WordPress, you can use the remove_role() function. This function accepts one parameter: the role. For example, to remove the ‘custom_role’ you added earlier, you would use the following code:

remove_role( 'custom_role' );

How can I add capabilities to a user role in WordPress?

To add capabilities to a user role in WordPress, you can use the add_cap() function. This function accepts two parameters: the capability and a boolean indicating whether the role has the capability. For example, to add the ‘publish_posts’ capability to the ‘custom_role’, you would use the following code:

$role = get_role( 'custom_role' );
$role->add_cap( 'publish_posts', true );

How can I remove capabilities from a user role in WordPress?

To remove capabilities from a user role in WordPress, you can use the remove_cap() function. This function accepts one parameter: the capability. For example, to remove the ‘publish_posts’ capability from the ‘custom_role’, you would use the following code:

$role = get_role( 'custom_role' );
$role->remove_cap( 'publish_posts' );

How can I change the default user role in WordPress?

To change the default user role in WordPress, navigate to Settings > General in your WordPress dashboard. Under ‘New User Default Role’, select the role you want to set as the default from the drop-down menu.

How can I assign multiple roles to a user in WordPress?

WordPress does not support assigning multiple roles to a user by default. However, you can use a plugin like Multiple Roles to achieve this. Once the plugin is installed and activated, you can assign multiple roles to a user from the user’s profile page.

How can I restrict content access based on user roles in WordPress?

To restrict content access based on user roles in WordPress, you can use a plugin like Members. This plugin allows you to control which roles can access specific content on your site.

How can I create a custom capability in WordPress?

To create a custom capability in WordPress, you can use the add_cap() function. This function accepts two parameters: the capability and a boolean indicating whether the role has the capability. For example, to add a custom capability called ‘manage_custom’ to the ‘custom_role’, you would use the following code:

$role = get_role( 'custom_role' );
$role->add_cap( 'manage_custom', true );

How can I check if a user has a specific capability in WordPress?

To check if a user has a specific capability in WordPress, you can use the current_user_can() function. This function accepts one parameter: the capability. For example, to check if the current user has the ‘manage_custom’ capability, you would use the following code:

if ( current_user_can( 'manage_custom' ) ) {
// The current user has the 'manage_custom' capability
}

Johan SatgéJohan Satgé
View Author

A web developer with a passion for WordPress, JavaScript, pragmatic coding and polished hand-crafted projects.

ChrisBWordPress
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week