I find the capabilities one of the most difficult to grasp concepts in WordPress development. On one hand, you have the capabilities of a custom post type and on the other hand you have the capabilities of a user or user role. And then you have the 2 other capabilities options you can set when registering a custom post type: map_meta_cap and capability_type. There are some good articles about this subject, but I did not find everything I needed. How do these things work?
Registering a custom post type without specifying anything about the capabilities
register_post_type('demo_article', [
'labels' => [
'name' => 'Articles',
'singular_name' => 'Article'
],
'public' => true,
]);
Now anyone who can edit posts can edit these Articles. You can see why if you look at the $cap
property of the $wp_post_types
global variable:
global $wp_post_types;
var_dump($wp_post_types['demo_article']->cap);
Output:
object(stdClass)[564]
public 'edit_post' => 'edit_post'
public 'read_post' => 'read_post'
public 'delete_post' => 'delete_post'
public 'edit_posts' => 'edit_posts'
public 'edit_others_posts' => 'edit_others_posts'
public 'publish_posts' => 'publish_posts'
public 'read_private_posts' => 'read_private_posts'
public 'read' => 'read'
public 'delete_posts' => 'delete_posts'
public 'delete_private_posts' => 'delete_private_posts'
public 'delete_published_posts' => 'delete_published_posts'
public 'delete_others_posts' => 'delete_others_posts'
public 'edit_private_posts' => 'edit_private_posts'
public 'edit_published_posts' => 'edit_published_posts'
public 'create_posts' => 'edit_posts'
Now a check like current_user_can($post_type_object->cap->edit_posts)
is translated to current_user_can('edit_posts')
.
Registering a custom post type with specifying capability_type
register_post_type('demo_article', [
'labels' => [
'name' => 'Articles',
'singular_name' => 'Article'
],
'public' => true,
'capability_type' => 'article'
]);
Now the $cap
property looks like this:
object(stdClass)[564]
public 'edit_post' => 'edit_article'
public 'read_post' => 'read_article'
public 'delete_post' => 'delete_article'
public 'edit_posts' => 'edit_articles'
public 'edit_others_posts' => 'edit_others_articles'
public 'publish_posts' => 'publish_articles'
public 'read_private_posts' => 'read_private_articles'
public 'create_posts' => 'edit_articles'
And finally setting both capability_type and map_meta_cap
register_post_type('demo_article', [
'labels' => [
'name' => 'Articles',
'singular_name' => 'Article'
],
'public' => true,
'capability_type' => 'article',
'map_meta_cap' => true
]);
Now the $cap
property looks like this:
object(stdClass)[862]
public 'edit_post' => 'edit_article'
public 'read_post' => 'read_article'
public 'delete_post' => 'delete_article'
public 'edit_posts' => 'edit_articles'
public 'edit_others_posts' => 'edit_others_articles'
public 'publish_posts' => 'publish_articles'
public 'read_private_posts' => 'read_private_articles'
public 'read' => 'read'
public 'delete_posts' => 'delete_articles'
public 'delete_private_posts' => 'delete_private_articles'
public 'delete_published_posts' => 'delete_published_articles'
public 'delete_others_posts' => 'delete_others_articles'
public 'edit_private_posts' => 'edit_private_articles'
public 'edit_published_posts' => 'edit_published_articles'
public 'create_posts' => 'edit_articles'
Now a check like current_user_can($post_type_object->cap->edit_posts)
is translated to current_user_can('edit_articles')
. This means that the logged in user should have the 'edit_articles' capability. This capability has to be added to the role of the user. For example if you want to add it to the administrator:
$admin = get_role('administrator');
$admin->add_cap('edit_articles');
Now you can play with the capabilities. For example if you want the editor to only allow to create articles and edit/delete his own unpublished articles:
$editor = get_role('editor');
$editor->add_cap('edit_articles');
$editor->add_cap('delete_articles');
Meta and primitive capabilities
There are different categories of capabilities: meta and primitive capabilities. There are 3 meta capabilities for (custom) posts and they are all singular: edit_post
, delete_post
, read_post
. You only want to set this to a custom post type and not to a user, because a meta capability is dynamically checked based on context. For example if the check is current_user_can('edit_post', 34)
WordPress uses the map_meta_cap
function to build the capability list with primitive capabilities that are just made for this context: edit this particular post by this particular user. So if the user is not the owner of this post and the user doesn't have the edit_other_posts
capability, the map_meta_cap function returns a list with edit_other_posts
in it, and current_user_can
will return false. The map_meta_cap
property makes sure WordPress does this translation. If you omit this property, WordPress would just look for the edit_article
capability and if this is not set to a user (which it should not), it will not match. Here is a good explanation.
Create a new role with specific capabilities
For example we want to add an article_editor role, that only has access to the articles section on the dashboard.
$caps = [
'read' => true
'edit_articles' => true,
'edit_others_articles' => true,
'publish_articles' => true,
'read_private_articles' => true,
'delete_articles' => true,
'delete_private_articles' => true,
'delete_published_articles' => true,
'delete_others_articles' => true,
'edit_private_articles' => true,
'edit_published_articles' => true
];
add_role('article_editor', 'Article Editor', $caps);
When should you add new caps and/or create new roles? Often this is done in the activation_hook
, and that works great for single site installations. But for multisite plugins, this hook isn't called, so beware of that.