3

I wanted to exclude pages from the search results, and found many ways to do it, and was wondering why use !is_admin() or is_main_query() and which way would be better.

add_filter('pre_get_posts','search_filter');
function search_filter($query) {
    if ($query->is_search) {
        $query->set('post_type', 'post');
    }
    return $query;
}

add_filter('pre_get_posts','search_filter');   
function search_filter($query) {
    if ( !is_admin() && $query->is_search) {
        $query->set('post_type', 'post');
    }
    return $query;
}

add_action('pre_get_posts','search_filter');    
function search_filter($query) {
if ( !is_admin() && $query->is_main_query() ) {
        if ($query->is_search) {
            $query->set('post_type', 'post');
        }
    }
}
6

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.

2
  • Thank you for the detailed explanation. I guess your first function is better approach if i want to future proof it, in case want to create custom post types. Just a question, are the default searchable post types the same as post types, or just 'pages' and 'posts'?
    – SilverLink
    Mar 14 '17 at 15:51
  • You're welcome. I think I would change the post type registration if possbile. The default searchable post types are post, page and attachment, if I recall correctly. @SilverLink
    – birgire
    Mar 14 '17 at 16:22
3

pre_get_posts will run in admin as well as the frontend, you can use it to filter posts that appear to the admin users as well as frontend results. Adding ! is_admin() will ensure that that code only affects the frontend.

is_main_query() will ensure that you are only affecting the query for the posts, not for example the menu items in the nav bar, or a list of posts in the sidebar. It is recommended to use that.

Let me know if this isn't clear,

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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