How to exclude pages from the search results

Note that when we use:

$query->set( 'post_type', 'post' );

then we’re overriding all searchable post types, not only the page post type.

That may be just fine in some cases, and we’re done using some of your pre_get_posts snippets that fit our needs.

But sometimes we don’t want to hard fix it that way. Here we discuss that kind of scenarios.

Using the register_post_type_args filter.

When the post type isn’t specified, the WP_Query search uses any post types that are searchable, namely:

$in_search_post_types = get_post_types( array('exclude_from_search' => false) );

When we register a post type, we can set the exclude_from_search parameter as false to exclude it from search.

We can modify it for the page post type setup with:

add_filter( 'register_post_type_args', function( $args, $name )
{
    // Target 'page' post type
    if( 'page' === $name )
        $args['exclude_from_search'] = true;

    return $args;
}, 10, 2 );

More about register_post_type() here.

Examples

Here are examples where the page post type would be excluded from the search, using the above filtering:

  • Main query search on the front-end with

    https://example.tld?s=testing
    
  • Secondary query like:

    $query = new WP_Query( [ 's' => 'testing' ] );
    
  • Secondary query like:

    $query = new WP_Query( [ 's' => 'testing', 'post_type' => 'any' ] );
    

Some notes on queries with pre set post types:

Let’s consider cases where the post types are fixed, like:

 $query = new WP_Query( [ 's' => 'testing', 'post_type' => [ 'page', 'post'] ] );

If the post type is set by some array $post_type, then we can filter the 'page' out it with

if( is_array( $post_type )  && count( $post_type ) > 1 )
{
    $post_type = array_filter( 
        $post_type, 
        function( $item ) { return 'page' !== $item; } 
    );
}

If we don’t have direct access to that array, we could use e.g. pre_get_posts to remove the ‘page’ from the post type array, with help of the get/set methods of WP_Query. Here’s an example for the main search query on the front end:

add_action( 'pre_get_posts', function search_filter( \WP_Query $query )
{
    if( ! $query->is_search() || ! $query->is_main_query() || ! is_admin() )
        return;

    $post_type = $query->get( 'post_type' );

    if( is_array( $post_type )  && count( $post_type ) > 1 )
    {
        $post_type = array_filter( 
            $post_type, 
            function( $item ) { return 'page' !== $item; } 
        );
        $query->set('post_type', $post_type );
    }

} );

Why did we check the array count > 1 here?

That’s because we should be careful to removing 'page' from examples like:

 $query = new WP_Query( [ 's' => 'testing', 'post_type' => [ 'page' ] ] );

 $query = new WP_Query( [ 's' => 'testing', 'post_type' => 'page' ] );

as an empty array or an empty string, for the post type:

 $query = new WP_Query( [ 's' => 'testing', 'post_type' => [] ] );

 $query = new WP_Query( [ 's' => 'testing', 'post_type' => '' ] );

will fall back to the 'post' post type.

Note that:

 $query = new WP_Query( [ 's' => 'testing', 'post_type' => 'page, post' ] );

isn’t supported, as the resulting post type would be 'pagepost'.

In these cases, where we don’t have direct access to the WP_Query objects, we could halt the query with tricks like 'post__in' => [] or 1=0 in the search WHERE query part or even play with the posts_pre_query filter or using some more advanced methods. There are plenty of answers on this site about that. This and this is what I recall at the moment.

The null case:

 $query = new WP_Query( [ 's' => 'testing', 'post_type' => null ] );

falls back to 'any' post types:

Hope it helps!

PS:

Also note the inconsistency in your snippets, as you have both

add_filter('pre_get_posts','search_filter');   

and

add_action('pre_get_posts','search_filter');   

It’s considered an action, but it will not make any difference, as actions are wrapped as filters, behind the scenes.

Leave a Comment