You’ll want to filter the main query via pre_get_posts
callback.
If you’ll only want to handle one specific post in this manner, you can reference/exclude its post ID directly. If, however, you want this same behavior for any future private posts, then I would query all posts with $post->post_status
of private
, and exclude them.
One post (where ID = 123):
function wpse99672_filter_pre_get_posts( $query ) {
if ( ! is_singular() && $query->is_main_query() ) {
$query->set( 'post__not_in', array( 123 ) );
}
}
add_action( 'pre_get_posts', 'wpse99672_filter_pre_get_posts' );
Or, for all private
posts:
function wpse99672_filter_pre_get_posts( $query ) {
if ( ! is_singular() && $query->is_main_query() ) {
// Query all post objects for private posts
$private_posts = new WP_Query( array( 'post_status' => 'private' ) );
// Extract post IDs
// Note: for performance, you could consider
// adding this array to a transient
$private_post_ids = array();
foreach ( $private_posts as $obj ) {
$private_post_ids[] = $obj->ID;
}
$query->set( 'post__not_in', $private_post_ids );
}
}
add_action( 'pre_get_posts', 'wpse99672_filter_pre_get_posts' );