WordPress search block to only search in a custom post type

There are times when I have wanted to add a search block to a post or page in WordPress and have it only return results from a specific post type, rather than searching all posts types that are included in search.

After a lot of back and forth I have managed to do this with the following code.

The resolves around adding a custom CSS class to your search block in the following format:

post-type--{post_type}

For example, to restrict the results to just a post type called movie, you would add the class of:

post-type--movie

You can also add multiple classes for restricting to more than one post type. For example adding these classes you restrict the results to posts and movies.

post-type--movie post-type--post

Here is the code you need, you can add this to your themes functions.php file or indeed in a plugin.

<?php
/**
 * Ensures the search block only searches for posts in the post type specified in the block class.
 *
 * @param  string $block_content The block content.
 * @param  array  $block         The block.
 * @param  array  $instance      The block instance.
 *
 * @return string                The block content.
 */
function hd_search_block_specific_post_type_only( $block_content, $block, $instance ) {

	// if this block has a custom classes added under advanced - do nothing.
	if ( empty( $block['attrs']['className'] ) ) {
		return $block_content;
	}

	// create an array to store post types in.
	$post_types = [];

	// split the class name at the space into an array.
	$classes = explode( ' ', $block['attrs']['className'] );

	// loop through each class.
	foreach ( $classes as $class ) {

		// if this class contains our special class.
		if ( false === strpos( $class, 'post-type--' ) ) {
			continue;
		}

		// split the class at the double dash.
		$class_parts = explode( '--', $class );

		// get the last part of the class - the post name.
		$post_types[] = $class_parts[1];

	}

	// if we have no post types.
	if ( empty( $post_types ) ) {
		return $block_content;
	}

	// check the post types exist.
	$post_types = array_filter( $post_types, 'post_type_exists' );

	// if we have no post types.
	if ( empty( $post_types ) ) {
		return $block_content;
	}

	// loop through each post type.
	foreach ( $post_types as $post_type ) {

		// replace the standard input with one that adds the post type.
		$block_content = str_replace( '<button aria-label="Search"', '<input type="hidden" name="post_type[]" value="' . esc_attr( $post_type ) . '"><button aria-label="Search"', $block_content );

	}

	// return the block content.
	return $block_content;

}

add_filter( 'render_block_core/search', 'hd_search_block_specific_post_type_only', 10, 3 );

If you are worried about your clients having to add the specific classes to a block, just create a block pattern, give it a sensible name (Search Movies for example) and they can simply add the pattern when they want to add a movie search to a post.

Update!

As it happens, there is an easier way to do this, but it does involve using Javascript.

This method involves creating a block variation of the search block. This is done using the code below, first enqueuing some javascript in the editor.

<?php
function hd_register_search_variation_js() {
	wp_enqueue_script(
		'hd_search_variation_js',
		get_template_directory_uri() . '/js/movie-search.js',
		array( 'wp-blocks' )
	);
}
add_action( 'enqueue_block_editor_assets', 'hd_register_search_variation_js' );

Then create a javascript file called movie-search.js and add this to your theme directories js folder.

In that file place the following:

wp.blocks.registerBlockVariation( 'core/search', {
	name: 'hd-search-movies',
	title: 'Search Movies',
	description: 'Displays a search input to search movies.',
	icon: 'search',
	attributes: {
		namespace: 'hd-search-movies',
		query: {
			post_type: 'movie',
		}
	},
});

The important part of that code is the query part where we set the post type.