WordPress capabilities and roles — add_role, remove_role, add_cap, remove_cap, current_user_can, user_can, WP_Roles, custom capabilities, administrator editor author contributor subscriber, capability mapping, meta capabilities, map_meta_cap, register_post_type capabilities, custom post type capabilities, WP_User roles and caps, ACF field group visibility by role. Invoke for: custom roles, multi-role sites, restrict content by capability, map_meta_cap, "hide ACF field from role", "register ca...
Scanned 9/11/2026
Install to Claude Code
npx -y skills add hekivo/superpowers-sage --skill wp-capabilities --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Wp Capabilities?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/hekivo-wp-capabilities)More formats (shields.io, HTML) on the badges page.
---
name: superpowers-sage:wp-capabilities
description: >
WordPress capabilities and roles — add_role, remove_role, add_cap, remove_cap,
current_user_can, user_can, WP_Roles, custom capabilities, administrator editor
author contributor subscriber, capability mapping, meta capabilities,
map_meta_cap, register_post_type capabilities, custom post type capabilities,
WP_User roles and caps, ACF field group visibility by role.
Invoke for: custom roles, multi-role sites, restrict content by capability,
map_meta_cap, "hide ACF field from role", "register capability for CPT".
user-invocable: false
---
# WordPress Capabilities and Authorization
## When to use
When implementing access control, permission checks, or role-based authorization in a Sage/Acorn project. This includes protecting REST API endpoints, restricting admin features, creating custom capabilities for custom post types, and bridging WordPress capabilities with Laravel-style Gates and Policies via Acorn.
## Inputs required
- The resource or action being protected (e.g., a CPT, an API endpoint, an admin page)
- The user roles that need access
- Whether custom capabilities are needed or built-in ones suffice
- Whether JWT middleware or Acorn guards are involved (cross-reference acorn-middleware)
## Procedure
### 1. Understand the capability hierarchy
WordPress authorization follows: **Roles -> Capabilities -> Meta Capabilities**.
- **Roles** are named collections of capabilities (administrator, editor, author, contributor, subscriber).
- **Capabilities** are granular permissions (edit_posts, manage_options, upload_files).
- **Meta capabilities** are virtual capabilities that map dynamically based on context (e.g., `edit_post` for a specific post maps to `edit_posts` or `edit_others_posts` depending on ownership).
### 2. Common capability groups
| Group | Capabilities |
|---|---|
| Options | `manage_options`, `manage_network_options` |
| Posts | `edit_posts`, `edit_others_posts`, `publish_posts`, `delete_posts`, `read_private_posts` |
| Pages | `edit_pages`, `edit_others_pages`, `publish_pages`, `delete_pages` |
| Users | `list_users`, `create_users`, `edit_users`, `delete_users`, `promote_users` |
| Uploads | `upload_files` |
| Plugins/Themes | `activate_plugins`, `edit_plugins`, `switch_themes`, `edit_themes` |
### 3. Check capabilities in code
```php
// In Service classes or controllers
if (current_user_can('edit_post', $post_id)) {
// User can edit this specific post (meta capability — ownership checked)
}
if (current_user_can('manage_options')) {
// User is an administrator
}
```
### 4. Register custom capabilities for CPTs (Poet)
When registering a CPT via Poet (`config/poet.php`), use `map_meta_cap` to enable granular capability mapping:
```php
// config/poet.php
'post' => [
'project' => [
'label' => 'Projects',
'capability_type' => 'project',
'map_meta_cap' => true,
// This generates: edit_project, edit_projects, edit_others_projects,
// publish_projects, delete_project, delete_projects, etc.
],
],
```
Then grant these capabilities to roles:
```php
// In a ServiceProvider boot() method
$editor = get_role('editor');
$editor->add_cap('edit_projects');
$editor->add_cap('edit_others_projects');
$editor->add_cap('publish_projects');
$editor->add_cap('delete_projects');
```
### 5. Role management
```php
// Add a custom role
add_role('project_manager', 'Project Manager', [
'read' => true,
'edit_projects' => true,
'publish_projects' => true,
'delete_projects' => true,
]);
// Remove a role
remove_role('project_manager');
// Add/remove capabilities from existing roles
$role = get_role('editor');
$role->add_cap('manage_project_settings');
$role->remove_cap('manage_project_settings');
```
**Important:** Role changes are written to the database. Run `add_role`/`add_cap` only once (e.g., on plugin activation or behind a version check), not on every request.
### 6. REST API permission callbacks
```php
register_rest_route('app/v1', '/projects', [
'methods' => 'POST',
'callback' => [$this, 'createProject'],
'permission_callback' => function (\WP_REST_Request $request) {
return current_user_can('publish_projects');
},
]);
```
### 7. Map capabilities to JWT middleware guards
When using JWT authentication (see acorn-middleware), map WordPress capabilities to middleware guards:
```php
// In a middleware class
public function handle($request, Closure $next, string $capability)
{
$user = wp_get_current_user();
if (!$user->exists() || !$user->has_cap($capability)) {
return response()->json(['error' => 'Forbidden'], 403);
}
return $next($request);
}
// Route registration
Route::middleware(['jwt.auth', 'capability:edit_projects'])
->post('/projects', [ProjectController::class, 'store']);
```
### 8. Gate/Policy pattern with Acorn
Use Laravel-style authorization on top of WordPress capabilities:
```php
// In AuthServiceProvider boot()
Gate::define('update-project', function ($user, $project) {
return current_user_can('edit_post', $project->ID);
});
// Usage in controller
if (Gate::allows('update-project', $project)) {
// Authorized
}
```
### 9. Meta capability mapping example
```php
add_filter('map_meta_cap', function (array $caps, string $cap, int $user_id, array $args) {
if ($cap === 'edit_project') {
$post = get_post($args[0]);
if ((int) $post->post_author === $user_id) {
return ['edit_projects'];
}
return ['edit_others_projects'];
}
return $caps;
}, 10, 4);
```
## Verification
1. Test with different user roles using `wp user create` and `wp role list` (WP-CLI).
2. Verify `current_user_can()` returns expected results for each role.
3. Confirm REST API endpoints return 403 for unauthorized users and 200 for authorized ones.
4. Check that custom CPT capabilities are properly mapped: `wp cap list editor`.
5. Use Query Monitor's "Capability Checks" panel to audit capability checks at runtime.
## Failure modes
- **Capabilities not persisting:** `add_cap()` writes to DB. If called repeatedly on every request, it causes unnecessary DB writes. Gate behind a version check or activation hook.
- **Meta capability not mapping:** Forgot `map_meta_cap => true` on CPT registration, so `current_user_can('edit_project', $id)` always returns false.
- **JWT user context missing:** `current_user_can()` returns false because `wp_set_current_user()` was not called after JWT validation. Ensure middleware sets user context.
- **Role removed but capabilities orphaned:** Removing a role does not remove its capabilities from users who were assigned that role. Clean up explicitly.
- **Super admin bypass:** In multisite, super admins bypass all capability checks. Account for this in testing.
## Escalation
- If capability checks pass in WP admin but fail in REST/Acorn routes, check that JWT middleware is correctly setting the WordPress current user before capability checks run.
- If custom CPT capabilities are not recognized, verify the CPT is registered with `capability_type` set to a unique slug and `map_meta_cap => true`.
- For complex multi-role authorization logic, consider whether Acorn Gates/Policies provide cleaner abstraction than raw `current_user_can()` checks scattered through code.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!