WordPress: Custom Post types and read permission in REST

I am building a plugin that aims to store TODOs as custom post-type items.

Naturally, I want those todos to be private to my users. So I go through this very thorough tutorial

  • I check the capability_type to my CPT
  • I add the capabilties mapping
  • I even check the map_meta_cap

And mission accomplished – the posts are not visible even for my admin user in wp-admin.

But… They are still visible in REST. What?

It took me 2 hours to figure this out, only after I have read the WP_REST_Posts_Controller that serves the CPTs by default:

public function check_read_permission( $post ) {
$post_type = get_post_type_object( $post->post_type );
if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
return false;
}

// Is the post readable?
if ( 'publish' === $post->post_status || current_user_can( 'read_post', $post->ID ) ) {
return true;
}

$post_status_obj = get_post_status_object( $post->post_status );
if ( $post_status_obj && $post_status_obj->public ) {
return true;
}

// Can we read the parent if we're inheriting?
if ( 'inherit' === $post->post_status && $post->post_parent > 0 ) {
$parent = get_post( $post->post_parent );
if ( $parent ) {
return $this->check_read_permission( $parent );
}
}

/*
* If there isn't a parent, but the status is set to inherit, assume
* it's published (as per get_post_status()).
*/
if ( 'inherit' === $post->post_status ) {
return true;
}

return false;
}

As you see, any post that has post_type publish will be publicly visible. Which is tricky, because WordPress does not let you remove those post statuses for historical reasons.

Implementing your own Controller

The solution is to implement your own rest controler that overrides this method with the rest_controller_class parameter of register_post_type.

class POS_CPT_Rest_Controller extends WP_REST_Posts_Controller {
public function check_read_permission( $post ) {
$post_type = get_post_type_object( $post->post_type );
if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
return false;
}

return current_user_can( 'read_post', $post->ID );
}
}

And use it when registering a custom post type:

        $defaults = array_merge(
array(
'show_in_rest' => true,
'public' => false,
'show_ui' => true,
'has_archive' => false,
'publicly_queryable' => false,
'rest_controller_class' => 'POS_CPT_Rest_Controller',
//'show_in_menu' => 'pos',
'rest_namespace' => $this->rest_namespace,
'labels' => $labels,
'supports' => array( 'title', 'excerpt', 'editor', 'custom-fields' ),
'taxonomies' => array( 'category', 'post_tag' ),
),
$args
);
register_post_type( $this->id,$defaults );

Leave a Reply