Opens in a new tab
  1. Home
  2. Guides
  3. Access
Access guide

Creating a custom WordPress role safely

Learn how to create custom WordPress roles safely with add_role(), capabilities, controlled updates and least-privilege permission rules.

  • Updated August 19, 2026
  • 15 min read
  • WordPress guide

Creating a custom WordPress role safely means doing more than inventing a new role name and giving it a handful of permissions. A WordPress role is essentially a reusable collection of capabilities, and those capabilities determine what users assigned to that role are allowed to do.

A custom role can be useful when the built-in Administrator, Editor, Author, Contributor and Subscriber roles do not match the workflow of your website.

For example, you may need roles such as:

  • Content Manager;
  • Support Agent;
  • Store Assistant;
  • Agency Client;
  • Documentation Editor;
  • Premium Member;
  • Course Manager;
  • Property Manager.

The risky part is not creating the role itself. The real risk comes from granting capabilities you do not fully understand, modifying privileged roles globally or writing role setup code that runs repeatedly and leaves permissions in a state nobody remembers configuring.

If roles need to be managed visually rather than maintained in PHP, TheOneWP’s Role Manager provides an interface for creating and editing roles and their capabilities without turning every permission change into a code deployment.

This guide explains how WordPress roles and capabilities work, how to create a custom role with add_role(), how to choose permissions safely, how to update an existing role and how to avoid the most common role-management mistakes.

What is a WordPress role?

A WordPress role is a named collection of capabilities.

A capability represents a specific permission such as:

read
edit_posts
publish_posts
delete_posts
manage_options

Instead of assigning every permission manually to every user, WordPress groups capabilities into roles.

For example, a user assigned the Editor role receives the capabilities associated with that role.

The official WordPress Roles and Capabilities documentation explains the underlying permission model.

Roles and capabilities are not the same thing

This distinction matters.

A role answers:

What type of user is this?

A capability answers:

What is this user allowed to do?

For example:

Role:
content_manager

Capabilities:
read
edit_posts
edit_others_posts
publish_posts

WordPress ultimately evaluates permissions through capabilities rather than treating role names themselves as universal security rules.

This is why authorization code should usually check the capability required for an action rather than assuming only one particular role is allowed to perform it.

When should you create a custom role?

A custom role is useful when none of the built-in WordPress roles accurately represents a real group of users.

Suppose a company has employees who should:

  • log in to WordPress;
  • edit posts created by other team members;
  • publish content;
  • upload media;
  • never install plugins;
  • never change themes;
  • never manage users.

Making those employees Administrators would give them far more access than necessary.

A dedicated role can provide only the permissions required for their work.

Follow the principle of least privilege

The safest rule when designing a WordPress role is:

Give the role only the capabilities it actually needs.

This is the principle of least privilege.

If someone needs to edit posts, that does not automatically mean they need:

manage_options
install_plugins
activate_plugins
edit_users
delete_users
switch_themes

Those capabilities affect completely different parts of WordPress.

A custom role should begin with the minimum required permission set and gain additional capabilities only when a real workflow requires them.

Create a custom role with add_role()

WordPress provides the add_role() function for creating roles.

The official add_role() documentation defines three main arguments:

add_role(
    $role,
    $display_name,
    $capabilities
);

For example:

add_role(
    'content_manager',
    'Content Manager',
    [
        'read'          => true,
        'edit_posts'    => true,
        'publish_posts' => true,
    ]
);

This creates a role whose internal key is:

content_manager

and whose visible label is:

Content Manager

Use a stable role slug

The first argument to add_role() is the role key.

Use a machine-friendly value such as:

content_manager

rather than:

Content Manager

A good role key normally uses:

  • lowercase letters;
  • underscores where necessary;
  • no spaces;
  • a stable identifier that will not need cosmetic renaming later.

The human-readable label can change without forcing you to redesign every piece of code that references the role slug.

A safer Content Manager example

Suppose your Content Manager needs to work with posts but should have no site-administration permissions.

A reasonable starting role might be:

add_role(
    'content_manager',
    'Content Manager',
    [
        'read'                   => true,
        'upload_files'           => true,

        'edit_posts'             => true,
        'edit_others_posts'      => true,
        'edit_published_posts'   => true,

        'publish_posts'          => true,

        'delete_posts'           => true,
        'delete_others_posts'    => true,
        'delete_published_posts' => true,
    ]
);

This role can manage editorial content without automatically receiving sensitive administration capabilities.

Do not copy Administrator capabilities blindly

A tempting shortcut is to duplicate the Administrator role and then remove one or two capabilities.

That is usually backwards.

Administrator includes powerful permissions related to areas such as:

  • plugins;
  • themes;
  • users;
  • options;
  • updates;
  • site configuration.

If the new role exists for content management, starting from Administrator can accidentally preserve capabilities the user never needed.

Start from the minimum required permission set instead.

Copying an existing role can still be useful

There are cases where a custom role is intentionally very similar to a built-in role.

For example, you may want:

Editor
+
one custom capability

In that case, retrieving the existing role’s capabilities can be reasonable.

A simplified example:

$editor =
    get_role(
        'editor'
    );

if (
    $editor
) {

    add_role(
        'senior_editor',
        'Senior Editor',
        $editor->capabilities
    );

}

You can then add or remove capabilities from the new role.

Inspect what you are copying first. Inheriting permissions you have not reviewed defeats much of the purpose of creating a deliberately scoped role.

add_role() does not continuously update an existing role

This is an important WordPress behaviour.

add_role() creates the role if it does not already exist.

Suppose you initially create:

add_role(
    'content_manager',
    'Content Manager',
    [
        'read'       => true,
        'edit_posts' => true,
    ]
);

Later you change your PHP to:

add_role(
    'content_manager',
    'Content Manager',
    [
        'read'          => true,
        'edit_posts'    => true,
        'publish_posts' => true,
    ]
);

You should not assume that repeatedly running add_role() will synchronize an already-existing role with the new capability array.

Once the role exists, modify the role explicitly.

Modify an existing role with get_role()

You can retrieve a role using:

$role =
    get_role(
        'content_manager'
    );

Then add a capability:

if (
    $role
) {

    $role->add_cap(
        'publish_posts'
    );

}

Or remove one:

if (
    $role
) {

    $role->remove_cap(
        'delete_posts'
    );

}

See the official references for WP_Role::add_cap() and WP_Role::remove_cap().

Role capability changes are persistent

If you run:

$role->add_cap(
    'publish_posts'
);

WordPress stores the changed role configuration.

The capability does not disappear simply because the PHP line stops executing on the next request.

Similarly:

$role->remove_cap(
    'publish_posts'
);

persists until the capability is explicitly restored.

Role setup code should therefore be treated as configuration or migration logic rather than something casually executed on every frontend request.

Do not modify roles on every page load

This is technically possible:

add_action(
    'init',
    function () {

        $role =
            get_role(
                'content_manager'
            );

        if (
            $role
        ) {

            $role->add_cap(
                'publish_posts'
            );

        }

    }
);

but it is not a good pattern for a capability that only needs to be configured once.

The role change is persistent, so performing the same configuration repeatedly is unnecessary.

Create roles during plugin activation

If the role belongs to a plugin, a clean approach is to create it when the plugin is activated.

For example:

register_activation_hook(
    __FILE__,
    function () {

        add_role(
            'content_manager',
            'Content Manager',
            [
                'read'                 => true,
                'upload_files'         => true,
                'edit_posts'           => true,
                'edit_others_posts'    => true,
                'edit_published_posts' => true,
                'publish_posts'        => true,
            ]
        );

    }
);

Now role creation happens as part of the plugin’s installation lifecycle rather than during ordinary page requests.

Should a plugin remove its custom role on deactivation?

Usually not automatically.

Deactivation does not necessarily mean permanent removal.

A site administrator may deactivate a plugin temporarily for:

  • debugging;
  • maintenance;
  • compatibility testing;
  • deployment;
  • troubleshooting.

If deactivation immediately deletes the role, users assigned to that role may suddenly lose expected permissions.

That can create more disruption than the temporary plugin shutdown itself.

Removing a role with remove_role()

WordPress provides remove_role() when a role genuinely needs to be deleted.

For example:

remove_role(
    'content_manager'
);

See the official remove_role() documentation.

This should be used carefully.

Do not remove a role while users still depend on it

Before removing a role, check whether users are assigned to it.

If a role disappears while accounts depend on it, those users may lose the permissions they previously received through that role.

A proper removal workflow may therefore require:

  1. finding users assigned to the role;
  2. deciding which replacement role they should receive;
  3. migrating those users;
  4. removing the old role only after the migration is complete.

Assign a role to a user

The WP_User class provides methods for assigning roles.

For example:

$user =
    new WP_User(
        $user_id
    );

$user->set_role(
    'content_manager'
);

Be careful with set_role().

The official WP_User::set_role() documentation explains that setting a role replaces the user’s existing role assignments with the new role.

That makes it appropriate when the intention really is to replace the user’s current role.

Adding a role is different from replacing a role

WordPress also provides:

$user->add_role(
    'content_manager'
);

See the official WP_User::add_role() documentation.

These operations are conceptually different:

set_role()
→ replace role assignment

add_role()
→ add another role

Use the method that matches the intended account model.

Multiple roles can merge capabilities

A user can receive capabilities from more than one assigned role.

WordPress merges role capabilities together with any individual user capabilities when calculating the user’s effective permissions.

The official WP_User::get_role_caps() documentation describes this process.

This means multiple-role setups deserve particular care.

For example:

Role A:
edit_posts

Role B:
manage_options

A user receiving both may effectively gain both permission sets.

That is useful when intentional and dangerous when accidental.

Use multiple roles deliberately

Some WordPress installations genuinely need users to keep one primary role while inheriting capabilities from another.

For example:

Primary role:
Editor

Additional role:
Shop Manager

TheOneWP’s Multi Role Assignment module provides an interface for assigning additional roles while preserving the user’s primary role.

The important point is not simply that several roles can be attached to one user.

You need to understand the resulting effective capability set, because permissions from the assigned roles can combine.

Do not check role names for sensitive authorization

You may encounter code like:

if (
    in_array(
        'administrator',
        $user->roles,
        true
    )
) {

    // Allow action.

}

This can be appropriate when the business rule genuinely depends on a specific role.

For authorization, however, the more robust question is usually:

Does this user have the capability required for this action?

WordPress provides capability checks specifically for this purpose.

For example:

if (
    current_user_can(
        'edit_posts'
    )
) {

    // User may perform this action.

}

Roles organize permissions. Capabilities should normally enforce them.

Custom capabilities are often better than hardcoded role checks

Suppose your plugin has a reporting screen.

Instead of hardcoding:

administrator
editor
shop_manager

throughout the code, you can create a custom capability:

view_company_reports

Then assign that capability to whichever roles should have access.

For example:

$role =
    get_role(
        'content_manager'
    );

if (
    $role
) {

    $role->add_cap(
        'view_company_reports'
    );

}

Your actual authorization check becomes:

if (
    ! current_user_can(
        'view_company_reports'
    )
) {

    wp_die(
        'You are not allowed to access this page.'
    );

}

Now the feature depends on permission rather than a hardcoded list of role names.

Custom capabilities do nothing by themselves

Creating:

view_company_reports

does not automatically make WordPress protect anything.

Custom capabilities become useful when your own code checks them.

You still need logic such as:

current_user_can(
    'view_company_reports'
);

around the functionality being protected.

Be extremely careful with manage_options

The capability:

manage_options

is commonly used by WordPress and plugins to protect important settings screens.

Do not add it to a custom role merely because a settings page refuses to appear.

Doing so may expose considerably more site configuration than intended.

If your own plugin needs a permission for one administrative feature, consider whether a dedicated custom capability would be more appropriate.

Be careful with user-management capabilities

Capabilities related to users deserve particular attention.

A role that can edit or promote other users can potentially affect the site’s privilege structure.

Before assigning user-management permissions, ask:

  • Does this role really need to create users?
  • Can it edit existing accounts?
  • Can it change roles?
  • Can it affect privileged users?
  • Can it delete users?

A content-management role rarely needs broad user administration.

Protect the Administrator role

Modifying Administrator capabilities is especially risky.

If you remove the wrong capability from Administrator, you can make important areas of WordPress inaccessible to the people expected to repair the configuration.

Before changing Administrator:

  • understand exactly what the capability controls;
  • test outside production;
  • have a recovery method;
  • avoid removing critical capabilities simply to hide interface elements.

Hiding an admin menu item and removing the underlying permission are not the same thing.

Never use menu visibility as permission enforcement

Suppose you hide a menu item from a custom role.

That does not necessarily mean the user cannot access the underlying page directly.

Real permission enforcement must happen through capabilities.

For example:

if (
    ! current_user_can(
        'view_company_reports'
    )
) {

    wp_die(
        'Unauthorized.'
    );

}

UI visibility is presentation.

Capabilities are authorization.

Test a custom role with a real account

Do not test everything while logged in as Administrator.

Create a test user and assign the new role.

Then test:

  • login;
  • dashboard access;
  • media uploads;
  • post creation;
  • post editing;
  • publishing;
  • deletion;
  • access to other users’ content;
  • custom plugin screens;
  • frontend restricted areas.

Use a private browser window so your Administrator session does not interfere with the test.

Test both what the user can do and what they cannot do

A permission test is incomplete if you only check successful actions.

You should also verify that the user cannot access restricted functionality.

For example, a Content Manager might be expected to:

CAN
✓ edit posts
✓ publish posts
✓ upload media

CANNOT
✗ install plugins
✗ edit users
✗ change themes
✗ modify global options

The denied side of the test is often more important than the allowed side.

Custom post types may have their own capabilities

Custom post types can use their own capability configuration rather than relying entirely on normal post capabilities.

For example, a custom post type may involve capabilities such as:

edit_products
publish_products
delete_products

depending on how it was registered.

If a custom role should manage a particular post type, inspect how that post type maps its capabilities before simply granting generic edit_posts.

A role may need plugin-specific capabilities

Plugins frequently register their own capabilities.

A role that works perfectly with WordPress core may still lack access to:

  • WooCommerce management screens;
  • SEO settings;
  • membership tools;
  • custom dashboards;
  • reporting systems;
  • internal workflow tools.

Do not guess capability names.

Check the plugin’s implementation or documentation before granting permissions.

How to inspect registered WordPress roles

WordPress exposes its registered roles through the roles API.

The wp_roles() helper returns the global WP_Roles instance.

See the official wp_roles() documentation.

For example:

$roles =
    wp_roles();

foreach (
    $roles->roles
    as $role_key => $role_data
) {

    echo esc_html(
        $role_key
    );

}

This can be useful when building interfaces that need to support both core and custom roles dynamically.

Do not hardcode only the five default roles

A real WordPress installation may include roles registered by:

  • WooCommerce;
  • membership plugins;
  • LMS plugins;
  • multilingual systems;
  • custom development;
  • other business applications.

Interfaces that manage roles should therefore query WordPress for currently registered roles rather than assuming only Administrator, Editor, Author, Contributor and Subscriber exist.

Create the role in a plugin, not a theme, when possible

If the role represents business behaviour, it generally belongs in a plugin rather than a theme.

A theme controls presentation.

A role such as:

content_manager

usually represents application logic.

Creating it inside the active theme’s functions.php means the setup code disappears when the theme changes.

A small custom plugin is usually a cleaner home.

A complete custom role plugin example

A small implementation could look like:

<?php
/**
 * Plugin Name: Site Roles
 */

defined(
    'ABSPATH'
) || exit;

/*
 * Create role during activation.
 */

register_activation_hook(
    __FILE__,
    function () {

        add_role(
            'content_manager',
            'Content Manager',
            [
                'read'                   => true,
                'upload_files'           => true,

                'edit_posts'             => true,
                'edit_others_posts'      => true,
                'edit_published_posts'   => true,

                'publish_posts'          => true,

                'delete_posts'           => true,
                'delete_others_posts'    => true,
                'delete_published_posts' => true,
            ]
        );

    }
);

This establishes the role once when the plugin is activated.

Updating a role in a plugin version

Suppose version 1.0 creates the role and version 1.1 introduces a feature that requires:

view_company_reports

Instead of relying on add_role() again, perform an explicit upgrade step.

Conceptually:

$role =
    get_role(
        'content_manager'
    );

if (
    $role
) {

    $role->add_cap(
        'view_company_reports'
    );

}

A real plugin can track an installed version so permission migrations run once when required.

This is safer than attempting to rewrite the complete role definition on every request.

Keep role migrations explicit

As a plugin evolves, document permission changes.

For example:

1.0
Created Content Manager

1.1
Added view_company_reports

1.2
Removed delete_others_posts

Role permissions are part of application behaviour.

They deserve the same deliberate migration strategy you would use for database or configuration changes.

Do not remove capabilities without considering existing workflows

A capability may already be used by:

  • custom PHP;
  • another plugin;
  • an admin menu;
  • a REST endpoint;
  • a frontend account screen;
  • a scheduled workflow.

Removing it can affect more than the screen you are currently looking at.

Audit where the permission is used before changing production roles.

Back up role configuration before major changes

On an established site, role configuration can become part of critical application behaviour.

Before making large changes:

  • back up the database;
  • document existing roles;
  • record important capabilities;
  • identify users assigned to each role;
  • test changes on staging first.

Roles are stored configuration, not disposable interface preferences.

Custom roles and login redirects

Once you create a custom role, you may also want users assigned to it to land somewhere specific after authentication.

For example:

content_manager
→ /wp-admin/edit.php

premium_member
→ /member-dashboard/

Role creation and login redirects are separate concerns, but they often work together.

See Redirecting users by role in WordPress for the complete redirect workflow.

When a role manager is better than custom PHP

Custom PHP is appropriate when roles are part of version-controlled application logic and changes should go through development and deployment.

A visual management interface can be more practical when:

  • roles change regularly;
  • administrators need to review permissions;
  • custom roles are created across several client sites;
  • capability sets need to be compared visually;
  • permission changes should not require editing PHP.

TheOneWP’s Role Manager module provides that administrative layer.

The underlying security model does not change:

Role
→ collection of capabilities

Capability
→ permission checked by WordPress or application code

The difference is simply whether that configuration is maintained in code or through a dedicated WordPress interface.

Using TheOneWP Role Manager

TheOneWP’s Role Manager is most useful when WordPress role configuration needs to remain visible and manageable without opening a PHP file.

A role-management workflow can then become:

Create or select role
↓
review its capabilities
↓
enable only required permissions
↓
assign users
↓
test allowed actions
↓
test denied actions

This does not make capability design automatic.

An administrator still needs to understand what a permission means before granting it.

The benefit is that the resulting role structure can be reviewed and maintained through a dedicated interface instead of being scattered across activation hooks and one-off snippets.

Custom roles and multiple role assignments

Some workflows need a user to keep a primary role while receiving permissions from one or more additional roles.

For example:

Primary role:
Editor

Additional role:
Shop Manager

That is different from replacing the user’s role entirely.

TheOneWP’s Multi Role Assignment module supports that model by preserving the primary role while allowing additional roles to contribute their capabilities.

Whenever multiple roles are involved, review the resulting effective permissions rather than evaluating each role in isolation.

Common mistakes when creating custom WordPress roles

1. Giving the role too many capabilities

Start small and grant permissions intentionally.

2. Using Administrator as the default template

You may accidentally inherit powerful permissions unrelated to the role’s actual job.

3. Running add_role() repeatedly and expecting updates

Once the role exists, update its capabilities explicitly.

4. Modifying role capabilities on every page load

Capability changes are persistent, so recurring setup code is normally unnecessary.

5. Using roles instead of capabilities for authorization

Check the capability required by the action whenever possible.

6. Treating hidden menus as security

Removing a menu item does not replace permission checks.

7. Removing Administrator capabilities casually

A bad change can lock privileged users out of important administration screens.

8. Deleting a custom role before migrating its users

Determine what happens to assigned accounts first.

9. Using set_role() when you intended to add a role

set_role() replaces existing role assignments. add_role() adds another role.

10. Testing only as Administrator

Always test permissions using an account that actually has the custom role.

11. Guessing plugin capability names

Use the plugin’s actual documented or registered capabilities rather than granting unrelated broad permissions.

A safe custom WordPress role checklist

Before deploying a custom role, verify:

  • The role has a stable machine-readable slug.
  • The visible role name is clear.
  • Only required capabilities are enabled.
  • Sensitive administration capabilities are excluded unless genuinely needed.
  • Custom capabilities are checked by the functionality they protect.
  • Role creation happens through controlled setup or activation logic.
  • Capability updates are explicit and versioned.
  • Administrator is not modified unnecessarily.
  • Users assigned to the role have been tested.
  • Denied actions have been tested as well as allowed actions.
  • Custom post type and plugin-specific capabilities have been reviewed.
  • Multi-role behaviour is understood if the site uses it.
  • A recovery path exists before major production changes.

A safe role is a permission model, not just a label

Creating a custom WordPress role safely is technically straightforward:

add_role(
    'custom_role',
    'Custom Role',
    [...]
);

The real work is deciding what belongs inside the capability array.

A well-designed role gives users exactly the access required for their responsibilities without turning every account into a reduced but still dangerously privileged version of Administrator.

Create roles deliberately, use capabilities for authorization, keep permission changes persistent but controlled, test real accounts and treat role modifications as application configuration rather than casual interface tweaks.

When the permission model belongs in code, WordPress provides the APIs required to create and migrate it safely. When roles need to be reviewed and maintained administratively, TheOneWP’s Role Manager provides a dedicated interface for the same underlying WordPress role and capability system.

For the underlying WordPress APIs, refer to the official add_role() documentation, the Roles and Capabilities handbook, and the references for WP_Role::add_cap() and WP_Role::remove_cap().

Simplify your WordPress stack

A modular WordPress toolkit. 104 focused tools.

Ultimately, you can build cleaner workflows, maintain fewer plugins and enable only the features each website actually needs.