Controlling WordPress Custom Post Types, Capabilities and Roles

We all know about Custom Post Types and how to create them, when it comes to creating generic custom post types without Capabilities all users except subscriber roles can publish, read, edit and delete them by default. What if I refer back to my previous post about roles and limited capabilities for schools and you wanted to create a custom post type that Teachers could read, edit and publish but Staff could only read? Well here’s were custom post types and capabilities gets interesting! Take the following basic custom post type for registering Lessons:

add_action( 'init', 'register_cpt_lesson' );

function register_cpt_lesson() {

    $labels = array( 
        'name' => _x( 'Lessons', 'lesson' ),
        'singular_name' => _x( 'Lesson', 'lesson' ),
        'add_new' => _x( 'Add New', 'lesson' ),
        'add_new_item' => _x( 'Add New Lesson', 'lesson' ),
        'edit_item' => _x( 'Edit Lesson', 'lesson' ),
        'new_item' => _x( 'New Lesson', 'lesson' ),
        'view_item' => _x( 'View Lesson', 'lesson' ),
        'search_items' => _x( 'Search Lessons', 'lesson' ),
        'not_found' => _x( 'No lessons found', 'lesson' ),
        'not_found_in_trash' => _x( 'No lessons found in Trash', 'lesson' ),
        'parent_item_colon' => _x( 'Parent Lesson:', 'lesson' ),
        'menu_name' => _x( 'Lessons', 'lesson' ),
    );

    $args = array( 
        'labels' => $labels,
        'hierarchical' => true,
        'supports' => array( 'title', 'editor' ),
        'public' => true,
        'show_ui' => true,
        'show_in_menu' => true,
        'show_in_nav_menus' => true,
        'publicly_queryable' => true,
        'exclude_from_search' => false,
        'has_archive' => true,
        'query_var' => true,
        'can_export' => true,
        'rewrite' => true,
    );
    register_post_type( 'lesson', $args );
}

The example above would give all users (above subscriber) full capabilities over the custom post type Lessons, meaning all can read, publish, edit and delete them. When we add ‘map_meta_cap, capabilities and capability_type’ to the $args array we can take more control over who gets what capability per role by using the same method referred to in roles and limited capabilities for schools.

Lets modify the $args first by adding map_meta_cap capabilities, and capability_type:

add_action( 'init', 'register_cpt_lesson' );

function register_cpt_lesson() {

    $labels = array( 
        'name' => _x( 'Lessons', 'lesson' ),
        'singular_name' => _x( 'Lesson', 'lesson' ),
        'add_new' => _x( 'Add New', 'lesson' ),
        'add_new_item' => _x( 'Add New Lesson', 'lesson' ),
        'edit_item' => _x( 'Edit Lesson', 'lesson' ),
        'new_item' => _x( 'New Lesson', 'lesson' ),
        'view_item' => _x( 'View Lesson', 'lesson' ),
        'search_items' => _x( 'Search Lessons', 'lesson' ),
        'not_found' => _x( 'No lessons found', 'lesson' ),
        'not_found_in_trash' => _x( 'No lessons found in Trash', 'lesson' ),
        'parent_item_colon' => _x( 'Parent Lesson:', 'lesson' ),
        'menu_name' => _x( 'Lessons', 'lesson' ),
    );

    $args = array( 
        'labels' => $labels,
        'hierarchical' => true,
        'supports' => array( 'title', 'editor' ),
        'public' => true,
        'show_ui' => true,
        'show_in_menu' => true,
        'show_in_nav_menus' => true,
        'publicly_queryable' => true,
        'exclude_from_search' => false,
        'has_archive' => true,
        'query_var' => true,
        'can_export' => true,
        'rewrite' => true,
        // map_meta_cap will allow us to remap the existing capabilities with new capabilities to match the new custom post type
        'map_meta_cap' => true
        // capabilities are what we are customising so lets remap them
        'capabilities' => array(
            'edit_post' => 'edit_lesson',
            'edit_posts' => 'edit_lessons',
            'edit_others_posts' => 'edit_other_lessons',
            'publish_posts' => 'publish_lessons',
            'edit_publish_posts' => 'edit_publish_lessons',
            'read_post' => 'read_lessons',
            'read_private_posts' => 'read_private_lessons',
            'delete_post' => 'delete_lesson'
        ),
        // capability_type defines how to make words plural, by default the
        // second word has an 's' added to it and for 'lesson' that's fine
        // however when it comes to words like gallery the plural would become
        // galleries so it's worth adding your own regardless of the plural.
        'capability_type' => array('lesson', 'lessons'),
    );
    register_post_type( 'lesson', $args );
}

So now we have a Custom Post Type with Capabilities remapped to our own custom Capabilities with our own plural words. All we have to do now is add the capability to the role in the same way we did previously with roles and limited capabilities for schools.

Just like so:

function manage_lesson_capabilities() {
    // gets the role to add capabilities to
    $admin = get_role( 'administrator' );
    $editor = get_role( 'editor' );
	// replicate all the remapped capabilites from the custom post type lesson
    $caps = array(
    	'edit_lesson',
    	'edit_lessons',
    	'edit_other_lessons',
    	'publish_lessons',
    	'edit_published_lessons',
    	'read_lessons',
    	'read_private_lessons',
    	'delete_lesson'
    );
    // give all the capabilities to the administrator
    foreach ($caps as $cap) {
	    $admin->add_cap( $cap );
    }
    // limited the capabilities to the editor or a custom role 
    $editor->add_cap( 'edit_lesson' );
    $editor->add_cap( 'edit_lessons' );
    $editor->add_cap( 'read_lessons' );
}
add_action( 'admin_init', 'manage_lesson_capabilities');

VoilĂ ! Not only have we created a neat custom post type we also have complete control over the capabilities the roles have over those custom post types.

About

A geek who likes to cook with wp ingredients!

7 thoughts on “Controlling WordPress Custom Post Types, Capabilities and Roles

  1. Thank you for sharing! Good you added your comments in the code, especially lines 34 and 47-50. For these who still find WordPress custom post types capabilities management too difficult to comprehend there is free plugin Types with a paid extension called Access.

  2. Thank you soo much Richmond, You saved me a lot of work. Things are working great.
    I have a problem though.
    /* CODE

    foreach($mapped_post_types as $key => $value){
    $post_type_role->add_cap( 'publish_'.$value.'s' );
    $post_type_role->add_cap( 'edit_published_'.$value.'s' );
    $post_type_role->add_cap( 'edit_'.$value );
    $post_type_role->add_cap( 'edit_'.$value.'s' );
    $post_type_role->add_cap( 'read_'.$value.'s' );
    $post_type_role->add_cap( 'read_private_'.$value.'s' );
    $post_type_role->add_cap( 'delete_'.$value );
    }

    1) I have 50 post types and 50 corresponding roles.
    2) I am getting all the post types with map_meta_cap set to true.
    3) I am looping through them one at a time.
    4) Getting relavent role (post type name and role name is same).
    5) Adding capabilities to that role.

    After implementing the above code, Once i refresh my admin panel, the wp admin is in loading state forever. However, if i commentout the above add_cap code and then var dump global $wp_roles, these capabilities got mapped and wp admin is loading.

    Whats the problem with my code?

    • Hi Naresh, sorry on the delay getting back to you! As this is a seasonal site I kind of let it lie after Christmas.
      Anyway, the function ‘add_cap’ is persistent, meaning, that it is saved to the database so you have to use ‘remove_cap’ to revoke the capability for a given role. There is more on the codex about this and best practices to use if you are using this in a plugin or a theme.
      See http://codex.wordpress.org/Function_Reference/add_cap

Leave a Reply to Carl Cancel reply

Your email address will not be published. Required fields are marked *