By default the WordPress search returns every public post type: posts, pages, products, portfolio items and whatever else your theme and plugins register. This snippet limits WordPress search results to the post types you choose, for example only posts, or posts and pages.
It only changes the site’s search results page. Searches in wp-admin, the REST API, widgets and custom queries in your templates keep working as before.
The snippet
<?php
/**
* Limit the WordPress front-end search to the post types you choose.
*/
// ===== Config =====
function dpc_search_post_types() {
// Post types the search returns, e.g. array( 'post' ) or array( 'post', 'page', 'product' ).
$post_types = array( 'post', 'page' );
// ===== End of config =====
return (array) apply_filters( 'dpc_search_post_types', $post_types );
}
add_action( 'pre_get_posts', 'dpc_limit_search_post_types' );
function dpc_limit_search_post_types( $query ) {
if ( is_admin() || ! $query->is_main_query() || ! $query->is_search() ) {
return;
}
// A search form that already asks for a post type (?post_type=product) is left alone.
if ( ! empty( $query->get( 'post_type' ) ) ) {
return;
}
$post_types = array_values( array_filter( dpc_search_post_types(), 'post_type_exists' ) );
if ( $post_types ) {
$query->set( 'post_type', $post_types );
}
}
Add it with Scripts Organizer
In Scripts Organizer go to Code Blocks → Add New and give the block a name. Set Trigger location to Everywhere, tick PHP under Script location, paste the code into the PHP editor, edit the config and publish. Leave Action/Hook empty.
The code only adds a pre_get_posts callback, which runs later when WordPress builds the search query. The Conditions trigger runs its PHP on the wp action, after the main query has already run, so it would be too late for this snippet.
The same code also works unchanged in a small plugin or an mu-plugin.
How it works
pre_get_postsruns for everyWP_Querybefore it reads the database. The snippet only acts on the main query of a front-end search (is_main_query()andis_search()), so a “Recent posts” list on the search page is not affected.- If the request already names a post type, as WooCommerce’s product search does with
?post_type=product, the query is left alone. post_type_exists()drops names that are not registered, such asproductwhen WooCommerce is off, so a typo cannot empty your results.- The older version of this post had no
<?phptag and a missing semicolon afteradd_filter(), which is a parse error. It also changed every search query on the site, not just the main one, and used the generic function namesearchfilter.
Customise it
- Only blog posts:
$post_types = array( 'post' );. - Include a custom post type: add its name, for example
array( 'post', 'page', 'docs' ). The name is the first argument of itsregister_post_type()call, and it is also in the URL of its list screen (edit.php?post_type=docs). - Search only post titles: see Limit WP Search to only Post Title.
- Highlight the search words in the results: see Highlight terms in the search results.
Gotchas
- This is not access control. A visitor can still search another public post type by adding
?post_type=to the URL. Content that must stay private needs to be non-public, not only excluded from search. - Block themes’ Search template uses the main query too, so the snippet works there. A Query Loop block set to a custom query is a separate query and keeps its own post type setting.
- Search plugins that replace the WordPress search (Relevanssi, SearchWP and similar) have their own post type settings; set the types there.
In the Scripts Organizer library Import it with one click from Scripts Organizer.