75

Given this custom post type:

register_post_type(
    'sample_post_type',
    [
        'labels' => [
            'name' => _x('Sample Posts', 'post type general name'),
            'singular_name' => _x('Sample Post', 'post type singular name'),
        ],
        'public' => true,
        'show_in_nav_menus' => false,
        'exclude_from_search' => true,
    ]
);

How can I disable the single post view for this particular post type? Displaying a simple 404 is fine, or redirecting to the homepage. Since this is a plugin, I can't create a single-sample_post_type.php file to set up an empty page.

9 Answers 9

85

To be able to disable the single view for a CPT you can either redirect users to a specific URL, or disable it while registering CPT itself.

METHOD 1:

Redirect CPT single to a custom URL, archive page is publicly available.

You can use template_redirect hook to redirect a user, you can use any URL you want in place of home_url() and the desired error code as 2nd argument.

<?php
add_action( 'template_redirect', 'wpse_128636_redirect_post' );

function wpse_128636_redirect_post() {
  if ( is_singular( 'sample_post_type' ) ) :
    wp_redirect( home_url(), 301 );
    exit;
  endif;
}
?>

METHOD 2:

Completely disable Single and Archive page from front-end; Works for Custom Post Types only.

An alternative approach is to set publicly_queryable to false while registering the custom post.

'publicly_queryable'  => false

This hides single as well as archive page for the CPT, this can be used for custom posts only.

Even though the archive and single is hidden, you can still add a page template or a custom block to list posts if needed.

6
  • 1
    A good solution. I found it was helpful to only perform this redirect for non-authenticated users, by checking to see if get_current_user_id() evaluates to 0.
    – RevNoah
    Commented Jun 29, 2015 at 13:25
  • 2
    Please check the answer form @Gustavo
    – Richard
    Commented Nov 18, 2016 at 9:17
  • 2
    why not use only is_singular('post-type-slug') in the if() condition instead of 2 conditions?
    – Akash K.
    Commented Oct 22, 2018 at 20:36
  • @AkashK. Yeah that can work as well, wasn't aware of the function.
    – Kumar
    Commented Oct 23, 2018 at 6:26
  • 1
    Missing closing bracket in code - should be... if ( is_singular( 'sample_post_type' ) )
    – neilgee
    Commented Aug 18, 2020 at 4:31
107

Just setting the argument

'publicly_queryable'  => false

when you call register_post_type()

4
  • 1
    This looks like the answer to stop your CPT from creating actual web pages while still creating all database references. Commented Sep 17, 2015 at 18:11
  • 54
    This hides archive too
    – spaceman
    Commented Oct 11, 2016 at 16:12
  • 1
    This should be the accepted answer. Commented Mar 16, 2018 at 10:26
  • For me this causes a redirect to the homepage instead of a 404. Any ideas why? I'm registering the post_type with Piklist, in case that's relevant. Commented Sep 9, 2018 at 21:27
17

Tested all the ones mentioned above and the actual solution is simpler than any redirects suggested.

In order to have archive be accessible and list the items, and single post not be accessible and auto redirect to 404 set

'query_var' => false

when registering your CPT. If you set publicly_queryable to false your archives will be redirected to home, any other combo wont work. Set the query_var to false and that is it.

Here is full CPT https://gist.github.com/danyj/bfd038d3c8d578548c4d700bd0a7942a

see line 50 https://gist.github.com/danyj/bfd038d3c8d578548c4d700bd0a7942a#file-thz_cpt_items_single_view_redirect-php-L50

as stated here

https://codex.wordpress.org/Function_Reference/register_post_type

Note: If query_var is empty, null, or a boolean FALSE, WordPress will still attempt to interpret it (4.2.2) and previews/views of your custom post will return 404s.

6
  • 2
    It feels a bit like a hack but it seems to work. Wish they'd just add a dedicated has_single property along with the existing has_archive property instead.
    – powerbuoy
    Commented Jan 31, 2019 at 13:40
  • 2
    Hi, I tried doing the same but it did not work. Despite adding the query_var to false, I can still visit the single page links. Can you help?
    – iSaumya
    Commented Oct 13, 2020 at 12:08
  • @iSaumya try to flush your permalinks (go to permalinks page and save again) or use wp-cli command wp rewrite flush && wp cache flush
    – thetwopct
    Commented May 28, 2021 at 16:23
  • 2
    No longer works (Aug 2022). The note that single views are removed no longer exists in documentation either.
    – Blorf
    Commented Aug 30, 2022 at 15:02
  • 1
    This is working for me, I have Elementor w/ JetEngine and I'm configuring the post type with public,publicly_queryable and has_archive set to true, with query_var set to false. My archives are working and my singles 301 to the homepage.
    – rtpHarry
    Commented Sep 11, 2022 at 12:59
10

A simpler way to do that can be passing the following args when registering the Custom Post Type

register_post_type('sample_post_type',array(
'labels' => array(
    'name' => _x('Sample Posts', 'post type general name'),
    'singular_name' => _x('Sample Post', 'post type singular name')
),
'public' => true,
'exclude_from_search' => true,
'show_in_admin_bar'   => false,
'show_in_nav_menus'   => false,
'publicly_queryable'  => false,
'query_var'           => false
));
3
  • 5
    You would actually need publically_querable to be true for Post Type Archives to work.
    – Howdy_McGee
    Commented Nov 12, 2014 at 19:42
  • 1
    This does not work for me. It justs redirects back to the homepage when trying to go to the archive Commented Jul 15, 2015 at 15:51
  • 2
    Setting query_var to false only disables using query vars in the url, ie /?sample_post_type=sample_slug, but not from accessing it via /sample_post_type/sample_slug/ Commented Jan 5, 2021 at 5:57
7

One. From your functions file.

add_action( 'template_redirect', 'redirect_cpt_singular_posts' );
    function redirect_cpt_singular_posts() {
      if ( is_singular('your-cpt-slug') ) {
        wp_redirect( home_url(), 302 );
        exit;
      }
    }

Two. From your single.cpt.php file:

<?php wp_redirect( home_url() ); exit; ?>
3
  • I liked second option. But I need to know is there any drawback with this second option. Commented Oct 10, 2017 at 6:27
  • @user2584538 If you don't have a custom single-cpt_name.php file you could not do it. If you put the function in a simple plugin file, you could activate/deactivate without editing any file.
    – EliasNS
    Commented Feb 5, 2018 at 10:12
  • Drawback would probably be SEO wised. For any of those answer. I would recommend adding the following meta tag <meta name="robots" content="none, nosnippet, noarchive" /> on each page you decide to redirect. Commented Jan 2, 2021 at 2:12
6

Working from of Sven's really good answer, I rewrote his function to make it easier to add multiple post types using in_array() in the if statement and then redirecting to the archive page instead of the home page.

(by the way, I think the setting query_var and/or publically_queryable to false will disable not only the single views, but also the native archive view, overriding 'has_archive' => true. In that case you can still set up a custom WP_query and create your own archive page, in a template, but the main query won't do that any more, will it?)

function fq_disable_single_cpt_views() {
  $queried_post_type = get_query_var('post_type');
  $cpts_without_single_views = array( 'my-post-type', 'my-other-post-type' );
  if ( is_single() && in_array( $queried_post_type, $cpts_without_single_views )  ) {
    wp_redirect( home_url( '/' . $queried_post_type . '/' ), 301 );
    exit;
  }
}

add_action( 'template_redirect', 'fq_disable_single_cpt_views' );
3
  • this was a good idea, but doesn't work if you change your redirect for the cpts
    – rudtek
    Commented Mar 23, 2020 at 17:57
  • 2
    I changed your wp_redirect line to wp_redirect( get_post_type_archive_link( $queried_post_type ), 301 ); works a charm!
    – rudtek
    Commented Mar 23, 2020 at 18:06
  • Nice. Good improvement.
    – slashbob
    Commented Mar 24, 2020 at 21:18
6

In case you want to completely disable custom post type single view on frontend but be able to display archive page things are getting a little complicated.

Setting publicly_queryable to false or rewrite to false will prevent for displaying both single and archive view. There is no flag in register_post_type function arguments to prevent creating only single view rewrite rules.

https://github.com/WordPress/WordPress/blob/5.2.3/wp-includes/class-wp-post-type.php#L540

However you can remove rewrite tag after registering your post type and this will leave archive view rewrite rules untouched but remove only single view rewrite rules.

/**
 * Register event post type
 */
function wpse_128636_register_event_post_type() {

    $labels = array(
        'name' => __( 'Events' ),
        'singular_name' => __( 'Event' ),
        'add_new' => __( 'Add new' ),
        'add_new_item' => __( 'Add new' ),
        'edit_item' => __( 'Edit' ),
        'new_item' => __( 'New' ),
        'view_item' => __( 'View' ),
        'search_items' => __( 'Search' ),
        'not_found' => __( 'Not found' ),
        'not_found_in_trash' => __( 'Not found Events in trash' ),
        'parent_item_colon' => __( 'Parent' ),
        'menu_name' => __( 'Events' ),

    );

    $args = array(
        'labels' => $labels,
        'hierarchical' => false,
        'supports' => array( 'title', 'page-attributes' ),
        'public' => true,
        'show_ui' => true,
        'show_in_menu' => true,
        'show_in_nav_menus' => true,
        'publicly_queryable' => true,
        'exclude_from_search' => true,
        'has_archive' => true,
        'rewrite' => array('slug' => 'event'),
        'capability_type' => 'post',
    );

    register_post_type( 'event', $args );
    remove_rewrite_tag( '%event%' ); // This line will remove event rewrite rules for single view
}

add_action( 'init', 'wpse_128636_register_event_post_type' );

Another bonus is that from now on you can create simple WordPress pages using event post type permalink structure (event/simple-page) which can be helpful in complex websites.

Remember to flush rewrite rules after code modification.

2
  • 1
    This should be the accepted answer, its short, simple and doesn't mess with archive pages. Commented Jun 12, 2020 at 20:17
  • For me, it turns the single pages into 404 pages, but YOAST SEO still places the links in its XML sitemap. Commented Apr 23, 2021 at 17:13
3

In WordPress 5.9.0 a new filter was added which allows not only disabling the single but removing any links to it from the admin screens.

add_filter( 'is_post_type_viewable', function( $is_viewable, $post_type ) {
    if ( 'sample_post_type' === $post_type->name ) {
        return false;
    }
    return $is_viewable;
}, 10, 2 );

The single won't load but if you enter the URL manually, the home page will load instead of a 404. To show a 404 instead of the home page, you may remove the rewrite rule after the post type is registered.

remove_rewrite_tag( '%sample_post_type%' );

After making these changes you will need to flush the rewrite rules.

Using these 2 methods will accomplish the goal without extra template, redirects and broken links in the admin.

0

My current solution (mostly "the redirect solution") with a slight implementation difference.

This will keep archive pages enabled (has_archive = TRUE)

But will allow you to enable or disable single pages when using register_post_type() with x_has_single = TRUE (the param is passed along with the post type object properties).

add_action( 'init', function() {
    $args = [
        ... // other vars
        'public'             => FALSE,
        'publicly_queryable' => TRUE,
        'show_ui'            => TRUE,
        'has_archive'        => 'customers',
        'rewrite'            => [ 'slug' => 'customers', 'with_front' => FALSE ],
        'x_has_single'       => FALSE,
    ];
    register_post_type( 'customers', $args );
} );

add_action( 'template_redirect', function() {
    $post_type = get_post_type() ?: FALSE;
    $post_type_obj = get_post_type_object( $post_type );
    $has_single = $post_type_obj->x_has_single ?? TRUE;
    if ( FALSE === $has_single && $post_type && is_singular( $post_type ) ) {
        wp_redirect( get_post_type_archive_link( $post_type ) ?: '/', 301 );
        exit;
    }
} );

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.