Limit WordPress search to specific post types

By default WordPress includes all public post types in search results — posts, pages, and any registered custom post types. On a standard blog that is usually acceptable, but on a more complex site it quickly becomes a problem. Imagine a site that has regular posts, documentation pages, a portfolio section, team member profiles, and an events calendar all living side by side. A visitor searching for a tutorial might get a mixed bag of portfolio items and team bios they have no interest in. The results feel irrelevant and broken, which drives users away instead of helping them find what they need. You might also want to exclude pages entirely and surface only posts, or build a custom search form that targets a single post type. WordPress fires the pre_get_posts action before every database query is assembled, including the main search query. This hook gives you direct access to the WP_Query object before it hits the database, so you can modify what gets fetched without rewriting the query from scratch. Using both is_search() and is_main_query() checks ensures your modification applies only to front-end search pages and not to admin queries or widget queries running in parallel on the same request. Setting the post_type parameter on the query restricts results to exactly the post types you specify. You can pass a single slug like 'post' or an array of multiple slugs. This technique is more reliable than filtering results after the query executes and works seamlessly with pagination and relevance ordering. The whole solution fits in a single short function.

Problem: WordPress search returns results from all public post types, but you only want it to search posts.

Solution: Add the following code to your functions.php file:

<?php
add_action( 'pre_get_posts', 'ha_limit_search_post_types' );

function ha_limit_search_post_types( $query ) {
    if ( ! is_admin() && $query->is_main_query() && $query->is_search() ) {
        $query->set( 'post_type', array( 'post' ) );
    }
}

NOTE: Pass an array of post type slugs to set( 'post_type', ... ) if you want to include multiple post types in the results. The ! is_admin() check is critical — without it this filter also affects admin search screens and posts may disappear from the backend list. Always combine it with is_main_query() to avoid interfering with secondary queries used by widgets or shortcodes on the same page.