I'm not entirely sure of the best way to approach the requirement so I'm hoping someone who has more experience with it will be able to share some gems. I've set up a custom post type with the following settings;

  • Public => True
  • Publicly Queryable => True
  • Has Archive => True
  • Exclude from Search => False

The posts are intended to be private and accessible to any role that has the capability of read_private_pages and read_private_posts. Users have to be logged in to view the content.

I'm also using the action pre_get_posts to filter the the query for the custom post type. In researching the options with checking if the page is part of the administrative interface, it seems that is_admin isn't reliable.

If yes, what's the best way to configure the custom post type without pre_get_posts affecting any administrative queries?

  • Using is_admin() in pre_get_posts hooks has always worked perfectly for me. Gutenberg and AJAX do complicate its use, but to avoid impacting list tables it will work.
    – Caleb
    Jun 28, 2023 at 13:41
  • Since it's a custom post-type can you not just generate a template to specifically handle that post type, then check if users are logged in, and if they are, check if they have the correct capabilities, then display the content, and if they fail either of the conditions, show them fallback "you can't see this" content? Most of what you're after, if I'm understanding correctly, is already handled by WP, so it's just a matter of determining when to display the content of the posts, vs. when to display a fallback message. Jun 28, 2023 at 14:26
  • @TonyDjukic, as I'm creating a custom post type with 'Has archive' enabled, it's accessible with the desired slug e.g. books. Since it's only meant to be accessible by roles with read_private_pages and read_private_posts, the first check is if( ( is_post_type_archive( 'books') ) && ! is_user_logged_in() ) { wp_safe_redirect( wp_login_url() ); exit;. It redirects the users to the login page. Since it's read_private_pages and read_private_pages, this natively allows administrators and editors to access the page. I've also granted it to subscribers.
    – Motivated
    Jun 29, 2023 at 9:50
  • The bit I'm unclear about is whether if( ! is_admin() && $query->is_main_query() ) { is correct, since the aim is to only affect the query for that page with pre_get_posts. If I'm reading your comment correctly, it sounds like I'm on the right track.
    – Motivated
    Jun 29, 2023 at 9:52

2 Answers 2


For the frontend, you can use template_redirect action to identify the page, and perform redirects or adjust the request on the fly (untested):

add_action( 'template_redirect', static function () {
    if ( ! is_singular( 'cpt' ) && ! is_post_type_archive( 'cpt' ) ) {

    if ( current_user_can( 'read_private_pages' ) || current_user_can( 'read_private_posts' ) ) {

    global $wp_query;
    status_header( 404 );
} );

For excluding from search, you'll have to use pre_get_posts to prevent the posts form being listed in the results, unless you change exclude_from_search to true in the CPT registration.

  • I don't see how this achieves the requirement I shared. I've included additional background to my feedback to TonyDjukic.
    – Motivated
    Jun 29, 2023 at 9:54
  • It would prevent users without the capabilities from accessing the archive and singular pages, keeping them private.
    – Caleb
    Jun 29, 2023 at 13:15
  • why wouldn't you do something like function dosomething() { if( ( is_post_type_archive( 'cpt' ) ) && ! is_user_logged_in() ) { wp_safe_redirect( wp_login_url() ); exit; } if ( current_user_can( 'read_private_pages' ) || current_user_can( 'read_private_posts' ) ) { // Do something } }
    – Motivated
    Jul 3, 2023 at 10:25
  • Why 404 vs do a redirect? Mimicking behavior of a page that does not exist discourages unauthorized users from thinking the page does exist. Of course you could replace the 404 lines with a redirect if you prefer.
    – Caleb
    Jul 3, 2023 at 13:12

Here ya go

function YOUR_POST_TYPE_force_type_private($post)
    if ($post['post_type'] == 'YOUR_POST_TYPE')
    $post['post_status'] = 'private';
    return $post;
add_filter('wp_insert_post_data', 'YOUR_POST_TYPE_force_type_private');

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.