Add Custom WordPress Roles With PHP
WordPress user roles provide a practical way to control what people can do inside the dashboard. Instead of giving every staff member administrator access, you can create focused permissions for editors, shop managers, support workers, event coordinators, or clients who need access to a limited part of a website.
A role is a named collection of capabilities, while a capability is an individual permission such as edit_posts, publish_pages, or manage_woocommerce. Customising these permissions programmatically gives you repeatable deployments across development, staging, and production environments.
This approach suits Australian businesses that often have mixed teams: a Melbourne agency may manage several client sites, a Brisbane retailer may give warehouse staff restricted order access, and a Perth service company may need separate permissions for office staff and contractors. A carefully designed permission model improves security without slowing down routine work.
Understand Roles And Capabilities
A WordPress role describes a user category. Core roles include Administrator, Editor, Author, Contributor, and Subscriber. Plugins can add their own roles, such as WooCommerce’s Shop Manager. Each role contains an array of capabilities, and WordPress checks those capabilities before displaying screens or allowing actions.
Capabilities generally fall into two groups. Meta capabilities apply to a specific object, such as edit_post, while primitive capabilities describe broader permissions, such as edit_posts, publish_posts, or delete_posts. WordPress converts a request like “may this user edit post 245?” into a capability check that considers ownership, post status, and role permissions.
For example, a content assistant might need to write and edit posts but not publish them. A store operator may need to process orders without changing payment settings. Avoid treating a role name as a security control in application code. Check capabilities with current_user_can() instead:
if ( current_user_can( 'manage_options' ) ) {
// Display settings or perform an administrative action.
}
This makes code easier to maintain if a site later changes which roles receive the permission.
Register A Role During Plugin Activation
Custom roles should usually be registered by a plugin rather than a theme. Themes can be replaced, while business permissions generally need to remain available when the site changes its design. Use the plugin activation hook to create the role once:
<?php
/**
* Plugin Name: Store Content Permissions
*/
function scp_activate_plugin() {
add_role(
'store_content_editor',
'Store Content Editor',
array(
'read' => true,
'edit_posts' => true,
'edit_pages' => true,
'publish_posts' => false,
'publish_pages' => false,
'delete_posts' => false,
)
);
}
register_activation_hook( __FILE__, 'scp_activate_plugin' );
The first argument is the machine-readable role key. The second is the label shown to administrators, and the final argument contains capabilities. Role keys should be stable, lowercase, and prefixed to reduce conflicts with plugins. A prefix such as scp_ is safer than a generic key like manager.
add_role() does not need to run on every page request. Repeatedly adding a role is inefficient and can make permission changes difficult to reason about. When a role already exists, WordPress returns null, so use activation for initial setup and a versioned upgrade routine for later changes.
function scp_upgrade_roles() {
$version = get_option( 'scp_roles_version', '1.0' );
if ( version_compare( $version, '1.1', '<' ) ) {
$role = get_role( 'store_content_editor' );
if ( $role ) {
$role->add_cap( 'scp_view_reports' );
}
update_option( 'scp_roles_version', '1.1' );
}
}
add_action( 'init', 'scp_upgrade_roles' );
For a larger plugin, run this migration on activation and from an administrative update process. Keep the version number in the database so a capability adjustment happens once rather than on every request.
Add Capabilities And Assign Users
You can add a capability to an existing role with add_cap(). This is useful when a plugin introduces a permission that should be available to Editors or Shop Managers:
function scp_add_report_capability() {
$role = get_role( 'editor' );
if ( $role ) {
$role->add_cap( 'scp_view_reports' );
}
}
A custom capability should be checked wherever the protected feature is used. Hiding a menu item is helpful for usability, but it is not sufficient protection because a user could visit a URL directly.
if ( current_user_can( 'scp_view_reports' ) ) {
add_menu_page(
'Sales Reports',
'Sales Reports',
'scp_view_reports',
'scp-sales-reports',
'scp_render_reports'
);
}
You can assign a user to a role with set_role(), but be careful because it replaces the user’s existing roles. If someone is already an Editor and should gain an additional role, use add_role() on the user object instead:
$user = get_user_by( 'email', 'staff@example.com' );
if ( $user ) {
$user->add_role( 'store_content_editor' );
}
In a live Australian business, avoid assigning roles by email address in a page-load hook. Use a controlled migration, an administrator-only tool, or an onboarding process. This is especially important for organisations with casual staff, contractors, and rotating agency accounts in Sydney or Adelaide. Remove access promptly when a contract ends.
Control Custom Post Types Precisely
Custom post types need their own capability model when users should manage them independently of normal blog posts. Register the post type with capability_type and map_meta_cap:
function scp_register_documents() {
register_post_type(
'scp_document',
array(
'labels' => array(
'name' => 'Documents',
'singular_name' => 'Document',
),
'public' => false,
'show_ui' => true,
'show_in_menu' => true,
'supports' => array( 'title', 'editor', 'author' ),
'capability_type' => array( 'document', 'documents' ),
'map_meta_cap' => true,
)
);
}
add_action( 'init', 'scp_register_documents' );
This creates capabilities such as edit_documents, publish_documents, delete_documents, edit_document, and read_private_documents. Add the relevant primitive capabilities to your custom role during activation:
function scp_add_document_caps() {
$role = get_role( 'store_content_editor' );
if ( ! $role ) {
return;
}
foreach ( array(
'read',
'edit_documents',
'edit_document',
'edit_others_documents',
'publish_documents',
'read_private_documents',
) as $capability ) {
$role->add_cap( $capability );
}
}
The exact capabilities depend on the workflow. A document contributor might edit only their own drafts, while a compliance officer may edit documents written by other users but never publish them. Test ownership, draft status, scheduled content, trashed items, and private content with separate accounts.
For custom post type screens, the admin experience should be as clear as the permission model. If you are building a tailored dashboard for staff, a consistent navigation pattern can help users move between reports, documents, and orders; a navigation bar tutorial can provide useful interface ideas even though its implementation targets a different platform.
Protect Forms, Menus And Sensitive Actions
A capability check should be combined with a nonce and appropriate data validation whenever a user submits an administrative form. The nonce helps protect against cross-site request forgery, while the capability check determines whether the user is authorised.
function scp_save_settings() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( 'You are not allowed to update these settings.' );
}
check_admin_referer( 'scp_save_settings' );
$label = isset( $_POST['label'] )
? sanitize_text_field( wp_unslash( $_POST['label'] ) )
: '';
update_option( 'scp_report_label', $label );
}
Do not rely on is_admin() as an authorisation check. That function means the request is for the WordPress administration area; it does not mean the current user is an administrator. Similarly, checking a role directly can produce brittle code:
// Prefer this:
if ( current_user_can( 'scp_view_reports' ) ) {
// Allowed feature.
}
When collecting customer, employee, or membership information, consider the Australian Privacy Act 1988 and the Australian Privacy Principles. A role that can export orders or view customer notes may expose personal information, so give it only the access needed for the job. Keep audit logs for sensitive changes, restrict exports, and review access when staff leave.
WooCommerce stores also need practical separation between order processing and store configuration. A warehouse worker in Newcastle may need to update fulfilment details, but should not be able to alter tax, payment gateway, or administrator settings. Test third-party plugin capabilities because a plugin may introduce permissions that are broader than their labels suggest.
Test, Update And Remove Permissions
Test custom roles with a clean user account rather than an administrator account. Administrators often bypass restrictions, which can hide mistakes. Create test users for each role and verify the dashboard, direct URLs, AJAX actions, REST API endpoints, front-end forms, media library, and custom post type queries.
Use a staging copy before changing permissions on a production store. This is particularly useful for sites hosted across Australian time zones, where an unexpected migration during business hours could interrupt a retailer in Melbourne or a tourism operator in Cairns. Check scheduled publishing and cron tasks after deployment as well.
When a capability is no longer needed, remove it during a versioned upgrade:
function scp_remove_old_capability() {
$role = get_role( 'store_content_editor' );
if ( $role ) {
$role->remove_cap( 'scp_old_report_access' );
}
}
Removing a role is different from removing a capability. remove_role() deletes the role definition, but it does not necessarily provide the operational clarity of migrating users first. Before deleting a role, identify assigned users and move them to an approved replacement. Keep a rollback plan and document the reason for each permission.
A plugin deactivation hook can remove temporary data, but automatic permission removal on deactivation may surprise site owners. Many administrators deactivate a plugin while troubleshooting and expect their users to retain access when it is reactivated. Decide whether cleanup belongs on uninstall, and make that behaviour explicit in the plugin documentation.
Production Checklist For WordPress Access
A reliable role system is small, documented, and easy to audit. Give capabilities business-focused names, use prefixes, and separate content management from configuration management. For a membership site, for example, “manage memberships” should not silently imply access to payment settings or user deletion.
Keep the following checks in your deployment process:
- Register roles from a plugin and use activation or versioned migrations instead of every-request setup.
- Check capabilities at the server-side action, REST endpoint, AJAX handler, and administrative screen.
- Use nonces, sanitisation, validation, and escaping for every custom form and output.
- Test each role with a non-administrator account, including direct URLs and object ownership rules.
- Review staff access, export permissions, and personal-information exposure against Australian privacy obligations.
After deployment, inspect the Users screen and confirm that the intended accounts have the correct roles. Log important permission changes, keep plugin code under version control, and record which business process each capability supports. That documentation helps a future developer distinguish a deliberate restriction from an accidental configuration problem.
For WooCommerce and other plugin-heavy sites, recheck roles after major updates. Plugins can add new capabilities, alter menu visibility, or change how custom post types map permissions. A short quarterly review is often enough for a small Australian business, while larger agencies should include access reviews in every client maintenance cycle.