A freelancer needs to change one menu item, so they get an administrator account. The new writer only submits drafts, but Author sounded too junior and Editor about right, so now they can delete every post on the site. The role dropdown on the Add New User screen offers five words and explains none of them, which is how most WordPress sites end up with more administrators than people who should hold that power.
The five names hide a simple structure. A role in WordPress is nothing more than a named bundle of capabilities: individual yes or no grants with names like upload_files or moderate_comments. On a fresh single-site install, core hands out exactly 50 of them. Subscriber holds one. Contributor holds three. Author holds seven, Editor 26, Administrator all 50.
Once you see the bundles, the dropdown stops being a guess. What follows is what each role actually adds, why WordPress code checks capabilities rather than role names, and where roles physically live: one database row, which has consequences for how you create your own.
Five bundles, not a ladder
The default roles look like a ladder, and mostly behave like one: each role contains everything the role below it holds, plus a handful of new grants. But WordPress itself never treats them as ranks. Nowhere in core is there a comparison that asks whether one role outranks another. Every menu item, every button, every screen in the admin comes down to a single question, asked one capability at a time: may this user do this one thing?
The explorer below shows the whole picture: the five default roles against the 50 capabilities core really grants them, read from a live install rather than copied from a tutorial. Filter the matrix (type delete into the search and eleven of the 50 rows remain, which says something about what WordPress thinks needs guarding), diff any two roles, or assemble a role of your own and take the generated code with you. It runs entirely in your browser tab; nothing you type or tick is sent anywhere.
User roles explorer
The five default WordPress roles against the capabilities core really grants them, read from a fresh install. Compare two roles side by side, or tick together a role of your own and take the code with you. Everything runs in this browser tab, nothing is sent anywhere.
No capability name contains that, try a shorter fragment.
This is what a fresh single-site install grants. Plugins add their own capabilities on top, and the multisite-only ones stay out. Click a capability name for a one-line description. The level_0 to level_10 capabilities also still sit on every role, but they are a leftover from before roles existed and have been legacy since WordPress 3.0, so the matrix leaves them out.
That is the same role twice, so there is nothing to compare.
Each column lists what its role may do and the other may not. A capability both roles share appears in neither column.
Lower case a to z and underscores only: shop_manager works, Shop Manager does not.
Picking a base role ticks its capabilities below as a starting point. After that, every box is yours.
add_role() writes the role to the database, so it belongs in an activation hook or a one-off snippet, never in code that runs on every request; the snippet below uses register_activation_hook. remove_role() is the counterpart for taking the role out again and waits in a comment line at the end.
What each role actually adds
Subscriber holds a single capability: read. The name misleads. Nobody needs an account to read a public site; read gates access to the admin dashboard and the profile screen, nothing more. A subscriber can log in, change their own password and display name, and that is the entire job description. It is also the safety default: self-registrations receive whatever the default_role option holds, and on an untouched install that is subscriber.
Contributor adds edit_posts and delete_posts. Both apply to the contributor’s own unpublished posts: they can write drafts, edit them, delete them, and submit them for review. They cannot publish, and, the detail that surprises everyone, they cannot upload files. Without upload_files there is no Add Media button, so every image in a contributor’s draft has to be placed by someone with a bigger bundle. Whether that is a bug or the whole point depends on how much you trust your contributors.
Author is the first role that can put something live. It adds upload_files, publish_posts, edit_published_posts and delete_published_posts. Note the last two: an author can edit or delete their own posts after publication, indefinitely. If your workflow assumes published content is frozen, the Author role does not enforce that.
Editor jumps from seven capabilities to 26; the Compare tab’s preset, editor against author, lists all 19 additions in one column. They fall into three groups. Other people’s content: edit_others_posts, delete_others_posts and the private variants. Pages: contributors and authors cannot touch pages at all; everything from edit_pages to publish_pages arrives with Editor. And site housekeeping: moderate_comments, manage_categories, manage_links. The quiet one is unfiltered_html: on a single site, editors may save raw HTML including script tags. An editor account is a security boundary, not just a workflow tier.
Administrator adds the remaining 24, and not one of them is about content. Plugins: install, activate, update, delete, edit. Themes: switch, install, edit, plus edit_theme_options, which covers menus and widgets. Users: create, edit, promote, delete. Then manage_options for every settings screen, update_core, import, export, unfiltered_upload and edit_files. Each one changes what the site is rather than what it says.
Two footnotes from the live list. First, wp cap list also prints level_0 through level_10: leftovers of the user level system that roles replaced, deprecated since WordPress 3.0 and kept only so ancient plugins do not break. Ignore them. Second, upload_files opens the media library but says nothing about file types; uploads still pass a MIME whitelist, which is why an SVG upload fails even for an administrator.
Code checks capabilities, not roles
Every gate in WordPress runs through one function: current_user_can(), defined in wp-includes/capabilities.php. Menu registrations declare the capability a user needs before the screen appears. The editor asks before showing the Publish button. Plugins ask before rendering their settings pages. The question is always a capability, never a role.
if ( current_user_can( 'edit_others_posts' ) ) {
// show the review queue
}
You can watch this in plugin code everywhere: add_menu_page() takes a capability as its third argument, and most settings screens pass manage_options, which is why entire admin menus vanish the moment you log in as an editor. Nothing was hidden from a role. Every item asked its own question and got a no.
For anything that touches a specific object there is a second layer. current_user_can( 'edit_post', 42 ) does not look up a stored capability called edit_post; it hands the request to map_meta_cap(), which inspects post 42 and decides which primitive capabilities are actually required. Your own draft needs edit_posts. Someone else’s draft needs edit_others_posts on top. A published post adds edit_published_posts. That resolution step is the reason the stored capabilities come in those repetitive triples, and it is why the matrix looks the way it does.
Passing a role name, as in current_user_can( 'editor' ), happens to work because the role slug sits in the user’s capability list, but core’s own documentation warns against relying on it. It also breaks in practice: capabilities can be granted to a single user with no role change at all (WP_User::add_cap() writes into usermeta), and a custom role with editor-equivalent grants would fail every such check while passing every real one. Check the capability that gates the action. The role is just packaging.
Roles live in the database, not in code
The bundles are not hardcoded anywhere. At install time, populate_roles() in wp-admin/includes/schema.php writes all five default roles into a single option named user_roles with your table prefix in front, so the row is usually called wp_user_roles. One serialized PHP array holds every role, every display name and every grant. On the install behind this article that row has grown past 11 KB, because every plugin that registers its own capabilities appends them to the same array. Serialized also means byte-counted, which is why this exact row is a classic casualty of careless search and replace during a migration.
The option is only half of the picture. Which bundle a given user holds lives on the other side of the join, in a usermeta row named capabilities (again with the table prefix), and it is another serialized array, usually containing nothing but the role slug: a:1:{s:6:"editor";b:1;}. When WordPress builds a user object it merges that row with the roles option into one flat capability list, and that merged list is what current_user_can() actually consults. Promoting or demoting someone rewrites one usermeta row and touches nothing else, and a per-user add_cap() lands in the same row, next to the slug.
Storing roles as data has two consequences that catch almost everyone writing their first add_role() call. The first: add_role() writes to that option, so it only needs to run once, ever. The second: once the role exists, add_role() returns null and changes nothing. Paste it into functions.php, tweak the capability array a week later, and your edit silently never lands: the copy in the database wins. The clean pattern runs the call in an activation hook and keeps remove_role() ready for the day you want to rebuild it:
register_activation_hook( __FILE__, function () {
add_role( 'content_manager', 'Content Manager', array(
'read' => true,
'edit_posts' => true,
'edit_others_posts' => true,
'upload_files' => true,
) );
} );
// Counterpart, for deactivation or a rebuild:
// remove_role( 'content_manager' );
The Build tab of the explorer emits exactly this shape: pick a base role to copy its ticks, adjust the checkboxes, and the snippet updates with the activation hook wrapper and the remove_role() counterpart as a comment. The slug field enforces what WordPress expects, lowercase letters and underscores, before any code appears. And if you later register a custom post type with its own capability_type, the same logic extends: the post type mints new capability names such as edit_products, and some role has to be granted them or nobody sees the screen.
The account you use every day
The practical payoff of all this is a habit: stop working as an administrator. Even on a site you own alone, create a second account with the Editor role and write with that one. Every capability you carry while logged in is attack surface. A forged request or a stolen session cookie inherits your bundle, and an editor session cannot install a plugin, edit a theme file or create a new administrator. You lose nothing day to day; the admin account is still there for the Tuesday you actually update something.
For everyone else, match the bundle to the job, and reach for a single capability before a bigger role. The freelancer from the first paragraph needs menus, and menus are gated by edit_theme_options: one administrator-only capability, not all 24. A role holding read plus edit_theme_options, assembled in the Build tab in a minute, does that job with no route to the plugin screen. (One note for multisite: super admin is not a sixth role but a network-wide flag stored outside the roles option, and ordinary administrators lose several of the 50 there because plugin and theme powers move up to the network.)
The dropdown will stay five unexplained words, but the structure underneath is small enough to actually know: 50 grants, five bundles, one serialized row, and a single function asking one capability at a time. That is the entire system. For most sites it makes a role management plugin unnecessary; a ten line activation hook covers what those plugins are usually installed for.
The role dropdown trains you to ask which of five words a person is. The capability list asks a better question: which actions does this job need, and what is the smallest bundle that covers them? On most sites the honest answer involves fewer administrators than exist today, one custom role that should have been created years ago, and a site owner who writes as an editor and logs in as an administrator twice a month.