Devlog

WordPress Users, Roles and Capabilities

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.