WordPress Custom Roles and Capabilities Example: Least-Privilege Access
Create custom WordPress roles and capabilities with least-privilege access, activation hooks, capability checks, migrations, and testing steps.
Published
April 20, 2026
Reading Time
3 min read
Updated
April 20, 2026

Hardening Notes
Baselines, access reduction, and default settings that stand up in production.
Best For
Teams preparing, launching, or maintaining WordPress as a backend service in a production stack.
Primary Topics
Editorial Focus
Control Ledger: Baselines, access reduction, and default settings that stand up in production. Updated on April 20, 2026.
Full Report
Last reviewed: April 20, 2026
WordPress roles and capabilities are the difference between “everyone is an administrator” and a site that can be operated safely. Many teams create editor accounts for convenience, then discover that the role can do too much or too little for the real workflow.
This guide shows how to create a custom role and capability model for a plugin. The goal is least-privilege access: users should have the permissions they need, without gaining unrelated administrative power.
Who this guide is for
This article is for WordPress plugin developers, agencies, publishers, and site operators who need custom editorial, support, moderation, or operational roles.
Role vs capability
A role is a named bundle of capabilities. A capability is the permission checked in code. Production code should usually check capabilities, not role names. A user may have multiple roles, custom capabilities, or a role modified by another plugin.
if ( ! current_user_can( 'vulnwp_manage_reviews' ) ) {
wp_die( esc_html__( 'You are not allowed to manage reviews.', 'vulnwp-roles' ) );
}
Create a custom role on activation
<?php
/**
* Plugin Name: VulnWP Review Role
* Description: Example custom role and capability setup.
* Version: 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
register_activation_hook( __FILE__, 'vulnwp_add_review_role' );
register_deactivation_hook( __FILE__, 'vulnwp_remove_review_role' );
function vulnwp_add_review_role() {
add_role(
'vulnwp_reviewer',
__( 'Security Reviewer', 'vulnwp-roles' ),
array(
'read' => true,
'edit_posts' => true,
'upload_files' => true,
'vulnwp_manage_reviews' => true,
)
);
$administrator = get_role( 'administrator' );
if ( $administrator ) {
$administrator->add_cap( 'vulnwp_manage_reviews' );
}
}
function vulnwp_remove_review_role() {
remove_role( 'vulnwp_reviewer' );
$administrator = get_role( 'administrator' );
if ( $administrator ) {
$administrator->remove_cap( 'vulnwp_manage_reviews' );
}
}
This creates a focused reviewer role and also gives administrators the custom capability. The plugin removes the role and custom administrator capability on deactivation. If the capability controls persistent business data, document whether it should remain after uninstall.
Use capability checks in admin pages
add_action( 'admin_menu', 'vulnwp_reviews_menu' );
function vulnwp_reviews_menu() {
add_menu_page(
__( 'Review Queue', 'vulnwp-roles' ),
__( 'Review Queue', 'vulnwp-roles' ),
'vulnwp_manage_reviews',
'vulnwp-review-queue',
'vulnwp_render_review_queue'
);
}
Using the custom capability in the menu registration keeps the screen hidden from users who do not need it. Still check capability inside the page callback as a defense-in-depth measure.
Use object-specific checks when possible
Some actions are not global. Editing a specific post should use a meta capability such as edit_post with the post ID.
if ( ! current_user_can( 'edit_post', $post_id ) ) {
wp_send_json_error(
array( 'message' => 'You cannot edit this post.' ),
403
);
}
This lets WordPress map the check to the correct primitive capabilities for that post, author, status, and role configuration.
Migration and upgrade rules
Roles are stored in the database. If a plugin changes its capability model in a later release, treat that as a migration. Do not assume every site has the same role state. Administrators may have edited roles manually, another plugin may have changed them, or a staging import may have copied old capabilities.
function vulnwp_roles_maybe_upgrade() {
$version = get_option( 'vulnwp_roles_version', '0' );
if ( '1.1.0' === $version ) {
return;
}
$reviewer = get_role( 'vulnwp_reviewer' );
if ( $reviewer && ! $reviewer->has_cap( 'vulnwp_export_reviews' ) ) {
$reviewer->add_cap( 'vulnwp_export_reviews' );
}
update_option( 'vulnwp_roles_version', '1.1.0' );
}
add_action( 'admin_init', 'vulnwp_roles_maybe_upgrade' );
Keep migrations idempotent. Running the same migration twice should not break access.
Testing workflow
- Create a user with the custom role only.
- Confirm the user can access only the intended admin screens.
- Try blocked actions directly by URL, AJAX, and REST if applicable.
- Test an administrator account after capability changes.
- Deactivate the plugin on staging and confirm the removal policy works.
Production checklist
- Check capabilities in code, not role names.
- Use custom capabilities for plugin-owned privileges.
- Add roles and capabilities on activation, not on every request.
- Use object-specific meta capability checks where possible.
- Test with real low-privilege accounts.
- Document who should receive each custom role.
- Review custom roles during access audits.
Common mistakes
- Giving everyone administrator access. Convenience increases incident impact.
- Checking role names directly. Capabilities are the correct authorization boundary.
- Adding capabilities on every page load. Role setup belongs in activation or controlled migrations.
- Forgetting administrators. Custom capabilities may need to be granted to administrator roles too.
- No removal policy. Decide what happens when the plugin is deactivated or uninstalled.
Related reading
This pairs with our administrator access audit checklist and the secure Admin AJAX example.


