Custom post type capabilities and roles in WordPress
Tags: wordpress, php, tutorial, webdev
Published: 2020-09-08 11:04:17

<!-- wp:paragraph -->
<p>We often use custom post types for various purposes. I believe all WordPress plugin developers love this feature. But many of us who are relatively new in WordPress development, probably not aware of custom capabilities and it's potentials. In this article, I will try to put some light on custom post type capabilities.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>Why use new capabilities for custom post type</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The heart of the question is why I should use new capabilities instead of the defaults. The answer is simple. Whenever you need precise control for different users, you need custom capabilities. For example, if your plugin have custom post types and you want to distribute specific tasks to different users, you need custom capabilities.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Let's say I have a post type <code>book</code> and only authors can create, edit books. I will assign custom capabilities to authors only. There is a brilliant plugin available to customize user roles and capabilities. It's called "<a href="https://wordpress.org/plugins/user-role-editor/">User Role Editor</a>". By default, WordPress assigns capabilities based on the user roles such as "admin", "editor", "author" and so on. To know about user role based capabilities, use <code><a href="https://developer.wordpress.org/reference/functions/get_role/">get_role()</a></code> function.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>How to create new capabilities</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Now that we have understood the potentials, let's see how we can create. When we register a new post type in a plugin, we can define custom capabilities. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>A custom post type needs to be registered with <code>register_post_type()</code> function and <code>init</code> action hook. For example, let's register a <code>book</code> post type with <strong>custom capabilities</strong> -</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>function book_post_type() {
    $args = array(
        'public'          => true,
        'label'           => __( 'Books', 'textdomain' ),
        'menu_icon'       => 'dashicons-book',
        'show_in_rest'    => true,
        'taxonomies'      => array( 'category' ),
        'capability_type' => array('book','books'), //custom capability type
        'map_meta_cap'    => true, //map_meta_cap must be true
    );
    register_post_type( 'book', $args );
}
add_action( 'init', 'book_post_type' ); //action hook</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>If you notice, we have used <code>'capability_type' =&gt; array('book','books')</code> which will create new capability type for the <code>book</code> post type. It is also important to use <code>'map_meta_cap' =&gt; true,</code> for custom post types. If you don't set it <code>true</code>, WordPress will not be able to map new capabilities. Default capability keys are -</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>/**
 * Default capability type keys
 * Will be mapped with our types which are "book"(singular), "books"(plural)
 */

&#91;edit_post]              => edit_book
&#91;read_post]              => read_book
&#91;delete_post]            => delete_book
&#91;edit_posts]             => edit_books
&#91;edit_others_posts]      => edit_other_books
&#91;publish_posts]          => publish_books
&#91;read_private_posts]     => read_private_books
&#91;delete_posts]           => delete_books
&#91;delete_private_posts]   => delete_private_books
&#91;delete_published_posts] => delete_published_books
&#91;delete_others_posts]    => delete_others_books
&#91;edit_private_posts]     => edit_private_books
&#91;edit_published_posts]   => edit_published_books</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3>The restrictions</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Now at this point, WordPress will not show the custom post type (book) for any logged in user. This is for security purposes. As we have added new capabilities, we need to tell WordPress which user have these capabilities.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>Add newly added capabilities for admin</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Admin users are at the top level with all default capabilities assigned to them. So admin users also should have all custom capabilities. Let's do that in an efficient way.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Role based capability can be added by the <code><a href="https://developer.wordpress.org/reference/classes/wp_role/add_cap/">WP_Role::add_cap( <em>string</em>&nbsp;$cap )</a></code> function. This is important that we hook this function on plugin or theme activation. We shall use a class based static methods for better code management. We need to add the following code to the main plugin file.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>// add custom capabilities to the admin on plugin activation
register_activation_hook( __FILE__, array( 'Admin_Roles', 'add_admin_capabilities' ) );</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3>The main class for admin role</h3>
<!-- /wp:heading -->

<!-- wp:code -->
<pre class="wp-block-code"><code>/**
 * Main class for ADMINISTRATOR
 */
class Admin_Roles {
    /**
     * Custom capabilities of custom post types
     */
    private static $customCaps = array(
        &#91; 'singular' => 'book', 'plural' => 'books' ],
    );

    /**
     * Add custom capabilities for admin
     */
    public static function add_admin_capabilities() {

        $role = get_role( 'administrator' );

        foreach( self::$customCaps as $cap ){
            
            $singular = $cap&#91;'singular'];
            $plural = $cap&#91;'plural'];

            $role->add_cap( "edit_{$singular}" ); 
            $role->add_cap( "edit_{$plural}" ); 
            $role->add_cap( "edit_others_{$plural}" ); 
            $role->add_cap( "publish_{$plural}" ); 
            $role->add_cap( "read_{$singular}" ); 
            $role->add_cap( "read_private_{$plural}" ); 
            $role->add_cap( "delete_{$singular}" ); 
            $role->add_cap( "delete_{$plural}" );
            $role->add_cap( "delete_private_{$plural}" );
            $role->add_cap( "delete_others_{$plural}" );
            $role->add_cap( "edit_published_{$plural}" );
            $role->add_cap( "edit_private_{$plural}" );
            $role->add_cap( "delete_published_{$plural}" );
            
        }

    }

}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Now that we have our class ready, we can add any number of post type capabilities for admin. We just need to add capability types to <code>private static $customCaps</code> variable. For example, a newly added custom post type capability can be added like this -</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>private static $customCaps = array(
    &#91; 'singular' => 'book', 'plural' => 'books' ],
    &#91; 'singular' => 'story', 'plural' => 'stories' ],
);
</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":3} -->
<h3>Deactivate and reactivate the plugin</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>As we have used <code>register_activation_hook()</code> function, you need to deactivate and then reactivate the plugin to make this code work. The activation hook will add all the new capabilities for the admin. From this point, you may use the "<a href="https://wordpress.org/plugins/user-role-editor/">User Role Editor</a>" plugin and control user roles and assign custom post type capabilities for them.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>Conclusion</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>All custom post types should have custom capability types as it gives the power to the plugin users. This particular method is very simple and manageable. I use this method for all of my plugins that registers custom post types. My recent plugin "<a href="https://templateartist.com/2020/08/31/cqfs-wp-quiz-plugin/">Classic Quiz Feedback Survey</a>" is also using this method. This plugin is now available on WordPress plugin directory.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>I hope you enjoyed this article and if you have any question, please ask in the comment section.</p>
<!-- /wp:paragraph -->