From superpowers-sage
Guides WordPress capabilities, roles, and authorization in Sage/Acorn projects: add/remove roles/caps, custom CPTs, meta caps, current_user_can checks, ACF visibility.
npx claudepluginhub codigodoleo/superpowers-sage --plugin superpowers-sageThis skill uses the workspace's default tool permissions.
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.
Registers and manages WordPress Abilities API: PHP abilities/categories with meta, REST exposure (/wp-abilities/v1), JS consumption (@wordpress/abilities), permission checks, and troubleshooting visibility issues.
Guides WordPress REST API endpoint creation with register_rest_route, WP_REST_Controller, authentication (JWT, Application Passwords), permissions, schema validation, debugging, and Acorn Routes comparison.
Develops WordPress plugins with structure patterns, hooks, security (nonces, sanitization, prepared $wpdb queries), REST API, custom post types, and Settings API.
Share bugs, ideas, or general feedback.
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.
WordPress authorization follows: Roles -> Capabilities -> Meta Capabilities.
edit_post for a specific post maps to edit_posts or edit_others_posts depending on ownership).| 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 |
// 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
}
When registering a CPT via Poet (config/poet.php), use map_meta_cap to enable granular capability mapping:
// 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:
// 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');
// 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.
register_rest_route('app/v1', '/projects', [
'methods' => 'POST',
'callback' => [$this, 'createProject'],
'permission_callback' => function (\WP_REST_Request $request) {
return current_user_can('publish_projects');
},
]);
When using JWT authentication (see acorn-middleware), map WordPress capabilities to middleware guards:
// 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']);
Use Laravel-style authorization on top of WordPress capabilities:
// 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
}
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);
wp user create and wp role list (WP-CLI).current_user_can() returns expected results for each role.wp cap list editor.add_cap() writes to DB. If called repeatedly on every request, it causes unnecessary DB writes. Gate behind a version check or activation hook.map_meta_cap => true on CPT registration, so current_user_can('edit_project', $id) always returns false.current_user_can() returns false because wp_set_current_user() was not called after JWT validation. Ensure middleware sets user context.capability_type set to a unique slug and map_meta_cap => true.current_user_can() checks scattered through code.