Limit WordPress Search Results to Posts Only

By default WordPress search returns posts and pages, and every public custom post type. When pages like Privacy Policy or Cart show up between your articles, you want to limit WordPress search results to posts only.

This snippet changes only the main search query on the front end, so the search results page lists posts. One config line lets you add custom post types instead.

The snippet

<?php
/**
 * Limit WordPress search results to posts, or to the post types you list.
 */

// ===== Config =====
function dpc_search_post_types() {
	// Post type slugs to search, e.g. array( 'post', 'product' ).
	$post_types = array( 'post' );
	// ===== 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 asks for a post type itself (e.g. a product search) keeps it.
	if ( $query->get( 'post_type' ) ) {
		return;
	}

	$query->set( 'post_type', dpc_search_post_types() );
}

How it works

  • pre_get_posts runs before every WP_Query. The snippet acts only on the main query of a front-end search (?s=), so admin lists, widgets, blocks and your own queries are left alone.
  • If the search request already names a post type, for example a WooCommerce product search that sends post_type=product, the snippet keeps it.
  • dpc_search_post_types() holds the list of post types; it can also be changed from other code with the dpc_search_post_types filter.

The older version of this post had two copies of the function with unprefixed names, one with a CPT_SLUG placeholder, which had to be edited before use and could not run together.

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 and publish. Leave Action/Hook empty. The search runs on the front end, where Everywhere code is loaded before the query runs.

The same code also works unchanged in a small plugin or an mu-plugin.

Customise it

Search posts and a custom post type: replace the list in the config. The slug is the post_type value in the URL of its admin list.

function dpc_search_post_types() {
	$post_types = array( 'post', 'testimonial' ); // Your post type slugs.

	return (array) apply_filters( 'dpc_search_post_types', $post_types );
}

Gotchas

  • A post type registered with exclude_from_search set to true can still be listed here; the snippet sets the types directly.
  • Search blocks and plugins that run their own query (AJAX live search, the REST API) do not use the main query, so they are not limited.
  • Attachments are only found if you add attachment and also allow the inherit post status, because media is not published like posts.

In the Scripts Organizer library Import it with one click from Scripts Organizer.

// newsletter

Three tested WordPress snippets a week

Blocks, security, performance, Tailwind, PHP and React. Copy, paste, ship. You’ll also get DPlugins product updates. No spam, and you can unsubscribe anytime.

// newsletter

Three tested WordPress snippets a week

Blocks, security, performance, Tailwind, PHP and React. Copy, paste, ship. You'll also get DPlugins product updates. No spam, and you can unsubscribe anytime.