WordPress User Roles and Capabilities: A Practical Guide to Access Control

by Francis Rozange | Oct 2, 2026 | WordPress

In the fall of 2025, attackers tried tens of thousands of times to open administrator accounts on WordPress sites that had never invited them. All it took was submitting a plugin’s registration form with one extra field added to the request: the desired role, “administrator”. The plugin accepted it without question.

That story sums up the subject of this guide. In WordPress, every access decision comes down to roles and capabilities. Understood properly, the mechanism protects a site effortlessly. Misused, by a plugin, a rushed developer or an overly generous administrator, it throws the door wide open.

This guide covers the default roles and what they really allow, the capabilities worth knowing by name, how WooCommerce adds its own, the right way to create a role in code, the case of application passwords, then a method for auditing users and offboarding a contractor, with the commands to do it.

Roles and capabilities: the two words that matter

A role is a named list of capabilities

A capability is a single permission: publishing a post, uploading a file, installing a plugin. A role is simply a name placed on a list of capabilities. The Editor, for example, bundles every capability needed to manage content from all authors.

There is no inheritance between roles in code. The Common APIs handbook presents roles as a hierarchy, each one taking the rights of the one below. That describes the default lists, and the roles documentation itself says no role should be considered senior to another. Each role is a flat list: removing a capability from Author does not remove it from Editor.

Code should never check a role. The current_user_can() documentation discourages testing a role name instead of a capability, because the result may be unreliable. You ask “can this user edit this post?”, never “is this user an Editor?”.

Where WordPress stores them

Role definitions live in a single database option, wp_user_roles with the default prefix. The roles assigned to each user, and any capabilities added to that user individually, are stored in the wp_capabilities user meta. On Multisite, each site has its own keys: the main site keeps the base prefix, the others get a numbered prefix such as wp_2_.

The Super Admin is the exception: it does not appear in the list of roles. It is a network option, site_admins, holding a list of usernames. That distinction matters when you audit a network, as our WordPress Multisite guide explains.

The old level_0 to level_10 keys survive in role definitions for backward compatibility. User levels have been deprecated since WordPress 2.0, and WordPress flags, in debug mode, any code that still checks a numeric level.

The six default roles, from Super Admin to Subscriber

The official documentation describes six roles. Five exist on a single site; the sixth, Super Admin, only appears on Multisite. On installation, WordPress automatically creates an Administrator account, and the role given to new sign-ups is set under Settings, General.

  • Super Admin: runs the whole network, creates and deletes sites, manages plugins and themes for everyone.
  • Administrator: access to every administration feature of a site, from settings to plugins and users.
  • Editor: publishes and manages all content, including other users’ content, moderates comments and manages categories.
  • Author: publishes and manages their own posts, and uploads media.
  • Contributor: writes and edits their own posts, without being able to publish them or upload files.
  • Subscriber: can only manage their profile.

These short descriptions hide important differences. Authors can upload files, Contributors cannot: a detail that matters when you open an account for an outside writer. Editors also hold a capability many people overlook, covered below, which lets them publish code.

Single site or Multisite: what Administrators lose

On a single site, the Administrator is effectively a Super Admin. On Multisite, Administrators lose a set of capabilities reserved for the network: updating core, plugins and themes, installing or deleting plugins and themes, editing their files, creating, editing or deleting users, and publishing unfiltered HTML.

That split is deliberate. On a network, a site’s Administrator is treated as untrusted with respect to the other sites. They manage their content and settings, but cannot do anything that would affect the whole network.

The capabilities worth knowing by name

About fifty capabilities make up the default roles. There is no need to learn them all. A dozen are enough to understand what an account can really do, and to spot a dangerous setting in a role editor plugin.

unfiltered_html: why an Editor can publish JavaScript

The unfiltered_html capability allows publishing HTML, and even JavaScript, in posts, pages and widgets, without WordPress cleaning it. On a single site, Administrators and Editors have it by default. On Multisite, only Super Admins do.

The consequence is rarely appreciated. The WordPress security team considers that an Editor inserting JavaScript is using an intended feature, not exploiting a flaw. Giving someone the Editor role therefore means trusting them with running code in your visitors’ browsers, and your administrators’.

If nobody needs that ability, a constant removes it from everyone, administrators included:

define( 'DISALLOW_UNFILTERED_HTML', true );

Publishing, editing, uploading

publish_posts gives access to the Publish button; without it, an account only produces drafts submitted for review. edit_others_posts allows editing other people’s content, and is also used to manage templates in the Site Editor. upload_files opens the Media Library and uploads.

Settings, plugins, appearance

manage_options opens the WordPress settings pages, and most plugin settings pages. Since WordPress 7.0, it also gives access to the Connectors screen, where API keys for AI services are kept, masked on screen but not encrypted in the database. install_plugins allows adding plugins, which amounts to running any code on the server.

edit_theme_options gives access to menus, widgets, the Customizer and the Site Editor. The documentation points out that it is not limited to the Site Editor: it is a broad capability, not one to hand out lightly.

The user management trio

list_users lets an account see the list of users. promote_users enables the role change menu, and does not depend on edit_users. That last one allows editing users, and it is the most sensitive: core’s own code warns that, without filtering, anyone holding it can turn another account into an administrator.

One more subtlety: on a single site, WordPress treats any user holding delete_users as a Super Admin. Adding that capability to a custom role therefore gives it Super Admin status wherever code calls is_super_admin().

Primitive and meta capabilities: how WordPress decides

The capabilities covered so far are called primitive: they are stored in roles. But code often asks for something else, for example current_user_can( 'edit_post', $post_id ). That capability, edit_post in the singular, exists in no role: it is a meta capability.

The map_meta_cap() function translates it into primitive capabilities based on context. To edit a post you wrote, you need edit_posts; if it is published, edit_published_posts as well; if it belongs to someone else, edit_others_posts. The function checks nothing itself: it says what is required, then WordPress compares that with the account’s rights.

This mechanism is why checks should always target the specific object. Checking edit_posts does not tell you whether a user can edit a given post; checking edit_post with its ID does.

The self-edit trap

One rule in map_meta_cap() lets every user edit their own account. In 2016, the User Role Editor plugin learned this the hard way: it protected a sensitive action by checking edit_user against an ID supplied in the request. By passing their own ID, any registered user got through the check and could obtain the Administrator role.

The fix replaced that check with the general edit_users capability. The lesson still holds: a meta capability answers the question asked, not necessarily the one the developer had in mind.

Roles added by plugins: the WooCommerce example

A plugin can create its own roles and add capabilities to existing ones. WooCommerce is the most widespread example: on installation, it creates the customer and shop_manager roles, shown as Customer and Shop manager in the interface, and adds its own capabilities, such as manage_woocommerce, to Administrator and Shop manager.

What a Shop manager can do

The Shop manager handles products, orders, coupons and WooCommerce settings. They also have almost all of an Editor’s rights over content, import and export, the user list and edit_theme_options. They do not have manage_options, nor unfiltered_html, nor the right to install plugins.

For users, WooCommerce gives them edit_users at runtime, then strictly limits its use: they can only edit customers, and can only assign the Customer role. Those safeguards disappear if WooCommerce is deactivated, along with the right itself. It is a solid role for a client who runs their store, as long as nobody mistakes it for read-only access.

The customer account and the admin-ajax door

The Customer role only holds read, like a Subscriber. WooCommerce adds limited rights on the fly over the customer’s own orders: viewing, paying, cancelling them. It also redirects customers who try to open the admin area to their “My account” page.

That redirect provides no protection. It deliberately exempts admin-ajax.php and admin-post.php, through which many plugin actions run. Every action must therefore check capabilities itself. The real case below shows what happens when it does not.

Finally, deleting WooCommerce does not delete its roles, unless the WC_REMOVE_ALL_DATA constant is enabled in the configuration file. Roles from long-gone plugins linger on many older sites this way.

A locked glass door bypassed by a thin beam slipping through a side gap

The real case: when a sign-up form handed out administrator accounts

King Addons for Elementor is an add-on plugin for the Elementor builder, installed on more than ten thousand sites. Among other things, it offers a login and registration form. Flaw CVE-2025-8489, scored 9.8 out of 10 and documented by Wordfence and the US NVD database, is a textbook case of mishandled roles.

One field too many in a request

The registration form sent its data to admin-ajax.php. The handling code actually did a lot right: it refused sign-ups if the site did not allow them, verified a security token, limited the number of attempts and sanitized incoming fields.

Then it read a user_role field from the request, with “subscriber” as the default value, and passed it as is to wp_insert_user(). That function applies whatever role it is given, without judgment. Adding user_role=administrator to the request was therefore enough to create an administrator account.

The protections in place could do nothing about it. Sanitizing a field guarantees it holds a clean string, not that the string is allowed. The security token, printed on a public form, only proves the request came from the page, not that its sender has any rights. Every version from 24.12.92 to 51.1.14 was affected.

A quiet fix, immediate exploitation

On July 24, 2025, a researcher, Peter Thaleikis, reported the flaw to the Wordfence bug bounty program. Paying Wordfence customers received a firewall rule on August 4, free users on September 3, after the usual thirty-day delay. The vendor released fixed version 51.1.35 on September 25.

That version’s changelog mentioned security improvements across the plugin, with no word about privilege escalation. No site owner could read urgency into it. The flaw was made public on October 30, 2025. That version did not settle everything either: another unauthenticated privilege escalation, CVE-2025-6325, still affected King Addons up to version 51.1.36.

According to Wordfence, attacks started the very next day, then turned massive on November 9 and 10. By early December, its firewall had blocked more than 48,400 exploit attempts. The sign to look for on an affected site: new administrator accounts nobody on the team recognizes.

With such an account, an attacker can upload a plugin or theme containing a backdoor, or edit content to redirect visitors. The fix fits in three lines: an allow-list of permitted roles, limited to “subscriber” and “customer”, falling back to “subscriber” for any other value.

Same pattern, same season

A few weeks earlier, the WP Freeio plugin, bundled with a premium freelance marketplace theme, showed the same mistake: its registration code copied the role sent by the form. The fix shipped on October 9, 2025, the flaw was published the next day, and attacks began the same day. Two different plugins made the same mistake, and each was attacked no later than the day after its flaw was published.

What WordPress 7.0 changed

The attack on King Addons relied on one condition: the “Anyone can register” setting had to be on. It was the first check in the vulnerable code. A site that did not need public registration and had closed it was not exposed.

WordPress 7.0, released in May 2026, removed the Administrator and Editor roles from the new user default role dropdown. It also added a critical Site Health alert when registration is open with a privileged default role. A value saved earlier, however, stays displayed and active.

These changes would not have stopped King Addons, which let the request impose any role, whatever the default role. They do, however, point to the right question: does your site really need open registration? Going further, the 7.2 roadmap plans to prevent a dangerous role from being saved as the default; the change was still under discussion in early October 2026. Our analysis of WordPress vulnerabilities by the numbers shows how heavily access control flaws dominate attacks.

Creating a custom role safely in code

add_role() on activation, not on every load

A role created with add_role() is saved to the database on the first call. Later calls do nothing, so the reference documentation advises changing roles on plugin activation rather than on every page load, and the Plugin Handbook warns that re-creating roles without a condition degrades performance considerably. Here is an example assembled from the official functions:

register_activation_hook( __FILE__, 'acme_add_roles' );
function acme_add_roles() {
	// The list form needs WordPress 6.9 or later.
	add_role( 'acme_reviewer', 'Reviewer', array( 'read', 'edit_posts', 'edit_others_posts' ) );
}

Since WordPress 6.9, capabilities can be passed as a plain list. On an older version, use 'capability' => true pairs. A false value explicitly denies a capability to the role.

Changing an existing role

A classic trap: add_role() never updates a role that already exists. It does nothing and returns null. To add or remove a capability, go through get_role(), then add_cap() or remove_cap(), in a versioned upgrade routine that only runs once:

add_action( 'plugins_loaded', 'acme_update_roles' );
function acme_update_roles() {
	if ( '2' === get_option( 'acme_roles_version' ) ) {
		return;
	}
	$role = get_role( 'acme_reviewer' );
	if ( $role ) {
		$role->add_cap( 'upload_files' );
	}
	update_option( 'acme_roles_version', '2' );
}

In a public form, never read the role from the request: set it, or pick it from an allow-list fixed in the code, as King Addons now does. On the admin side, to offer a list of roles, use get_editable_roles(), which the editable_roles filter lets you restrict.

Cleaning up on uninstall

Deactivating a plugin does not remove its roles. Permanent cleanup happens on uninstall, in an uninstall.php file or with register_uninstall_hook(), by calling remove_role(). Without it, a deleted plugin’s roles stay in the database, sometimes with capabilities nobody watches anymore.

To put the default roles back in their original state, WP-CLI offers wp role reset, which leaves custom roles alone. The Plugin Handbook, on the other hand, advises against removing the Administrator and Super Admin roles; for any other default role you remove, it asks you to keep the removal code in place, because a WordPress update could add the role again.

Role editor plugins

When you need to adjust capabilities without writing code, a role editor plugin does the job. The main ones, all active and compatible with WordPress 7.1 in October 2026, each have their strength.

  • User Role Editor: more than 700,000 installs, checkbox capability editing, per-user capabilities, multiple roles. Pro version from $29 per year.
  • Members: free, with explicit capability denial, role cloning and an emailed rescue link to recover the Administrator role.
  • PublishPress Capabilities: automatic backup of every change, so you can roll back, and hiding of interface elements. Pro version from $69 per year.
  • Advanced Access Manager: policy-based access control, more powerful and more complex. Its declared compatibility stopped at WordPress 7.1.0 in early October.

These plugins are administration tools installed in production, and therefore an attack surface in their own right. User Role Editor has fixed at least three privilege escalation flaws, in 2016, December 2024 and September 2026; in the latest, an account holding promote_users could give itself the Administrator role. Once roles are set, nothing requires keeping the plugin active.

To test a role, User Switching lets you take over an account’s identity without knowing its password, then switch back to your own. To keep a record of role changes, a logging plugin such as WP Activity Log or Simple History records who changed what, and when.

Application passwords: a key with full rights

Since WordPress 5.6, every account can create application passwords from its profile. They serve external tools that go through the REST API or XML-RPC: a mobile app, an automation service, an AI agent. They cannot be used to log in to the admin area through the usual form.

Their weakness fits in one sentence: an application password acts with every capability of the account it belongs to. Core offers no read-only application password. A password created on an administrator’s account for some service is therefore an administrator key, and one that bypasses two-factor authentication.

Good practice follows: create a dedicated account for each service, with the lowest role that works, and generate the password from that account. This becomes pressing with AI agents, as our guide to AI in WordPress and the Abilities API explains.

Disabling or restricting them

If no service needs them, one filter disables them for the whole site:

add_filter( 'wp_is_application_passwords_available', '__return_false' );

A second filter lets you reserve them for certain accounts, for example those who manage settings:

add_filter( 'wp_is_application_passwords_available_for_user', function ( $available, $user ) {
	return $available && user_can( $user, 'manage_options' );
}, 10, 2 );

The WordPress 7.2 roadmap plans an email alert when an application password is created. It is not available yet: in the meantime, auditing remains manual.

Auditing users and roles with WP-CLI

The five-minute audit

The command line gives you in seconds what the interface makes you hunt for screen by screen. These four commands cover the essentials: the list of administrators, capabilities added to an account outside its role, the contents of a role, and the list of roles on the site. One caveat: --origin=user reads the wp_capabilities key literally, so it returns nothing with another table prefix or on a secondary site of a Multisite network.

wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
wp user list-caps 12 --origin=user
wp cap list editor
wp role list

Look for an administrator nobody recognizes, a sensitive capability added to a single account, a role left behind by a removed plugin. For application passwords, wp user application-password list followed by the account shows the existing ones, with their last use date. Our selection of essential WP-CLI commands details the useful options.

When the Users screen lies

In September 2026, Wordfence described malware installed as a must-use plugin. It creates an administrator with an innocuous name, then hides it from the Users screen and the REST API, going as far as adjusting the displayed counts. According to Wordfence, querying the database directly is the reliable way to see that account.

wp user list goes through the same query machinery as the Users screen, and must-use plugins still load even with the option that skips plugins. The wp db query command, however, runs before plugins load. This read-only query lists administrators as they are stored in the database; check the prefix first with wp db prefix.

wp db query "SELECT u.ID, u.user_login, u.user_email, u.user_registered FROM wp_users u JOIN wp_usermeta m ON m.user_id = u.ID WHERE m.meta_key = 'wp_capabilities' AND m.meta_value LIKE '%administrator%'"

If this list differs from the wp user list output, treat the site as compromised until the gap is explained, starting with the measures in our guide to WordPress security hardening.

Least privilege for agencies and their clients

One person, one named account, the lowest role that works

The principle of least privilege, as NIST defines it, means giving each entity only the rights its function requires. OWASP adds an often forgotten point: regularly check that rights do not pile up over time.

Applied to WordPress, this gives a few simple rules. One account per person, never a shared account, so that logs and content reassignment mean something. One named administrator per agency technician. For the client’s team, Editor or Shop manager rather than Administrator. And temporary elevation removed as soon as the job is done.

The official hardening guide adds two measures: avoid obvious usernames such as “admin”, which get attacked first, and add two-step authentication on top of a strong password. The DISALLOW_FILE_EDIT constant also removes the built-in file editor from every account.

The offboarding checklist

When a contractor or team member leaves, the order of operations matters. Cutting access without closing sessions leaves a door open until they expire.

  1. Close their active sessions, so their current logins drop immediately.
  2. Delete their application passwords, which would otherwise survive a simple password change.
  3. Demote or delete the account, reassigning its content to another account.
  4. Change shared access outside WordPress: hosting, SFTP, DNS, third-party services.
  5. Renew the security keys in the configuration file if any doubt remains, which logs everyone out.
  6. Run the administrator audit again, through WP-CLI and the database.
wp user session destroy jdupont --all
wp user application-password delete jdupont --all
wp user delete jdupont --reassign=3
wp config shuffle-salts

The --reassign option is essential: without it, WP-CLI asks for confirmation, then deletes the account’s content. Its posts and pages go to the trash, but its media, like most of its other content types, are deleted permanently. On Multisite, deleting an account from one site only removes it from that site; its application passwords stay attached to the network account.

Summary table

Role or tool What it allows Main risk Recommended use
Administrator Everything, plugins and users included Code execution on the server Named accounts, two-factor authentication
Editor All content, unfiltered HTML JavaScript published in pages Trusted editorial leads
Author, Contributor Their own content Low, uploads for Authors Writers, outside writers as Contributors
Shop manager WooCommerce, content, customers Appearance, import and export Client team running the store
Subscriber, Customer Their profile, their orders Poorly protected plugin actions Only acceptable role for public sign-up
Application password The API with the account’s rights Full key, outside two-factor authentication Dedicated account per service, minimal role
Role editor plugin Capabilities without code Attack surface in production Configure, then deactivate

Frequently asked questions

Can I give an Editor access to plugins?

Technically yes, by adding activate_plugins or install_plugins. But installing a plugin amounts to running code on the server: that account effectively becomes an administrator. A second named administrator account, protected by two-factor authentication, is a better option.

Is the Shop manager role right for a client?

Yes, it is often the best choice for the team handling orders and products. It has no access to general settings or plugins. Keep in mind that it can change the appearance, import content and edit customer accounts.

Should registration be open?

Only if the site needs it, for a store, a members area or a forum. In that case, the default role must be Subscriber, or Customer with WooCommerce. The King Addons flaw, for instance, only worked when registration was open. For membership sites, our comparison of WordPress membership plugins helps you pick a serious tool.

Do application passwords bypass two-factor authentication?

Yes, by design: they serve the API, which does not go through the login form. The Two Factor plugin, published by WordPress.org, at least closes the door to the main password: for an account protected by two-factor authentication, it accepts only that account’s application passwords on the API by default. The two_factor_user_api_login_enable filter lets you change that behavior.

How do I repair an administrator stripped of its rights?

If you removed capabilities from the Administrator role by mistake, wp role reset administrator restores it to its original state. The command also strips capabilities added by plugins: on a store, restore WooCommerce’s with “Reset capabilities” under WooCommerce, Status, Tools. Without command line access, the Members plugin, if it was already active, emails a rescue link that restores the Administrator role.

Conclusion

WordPress roles are simple: lists of capabilities, stored in an option and a user meta. The danger comes from what people do with them. An Editor who can publish JavaScript, an application password created by an administrator, a role read from a public request, a forgotten contractor account: each of these details is enough to hand over an entire site.

Keep three reflexes. Check capabilities against specific objects, never role names. Give each person and each service the lowest role that works. And audit administrators regularly, including directly in the database.

Sources


LaFactory designs, builds and maintains WordPress and WooCommerce sites, and develops its own plugins. Talk to us about your WordPress project.

Francis Rozange

Former section editor at Libération, he runs LaFactory, an international web agency since 1996.

Cart