Category Specific Search Form

I include category landing pages in many of the websites I build. These transform category archives into engaging destinations that surface popular and seasonal articles, include SEO friendly content, and encourage deeper content discovery.

While the homepage might include the standard search form that searches through all site content, these category landing pages need a category-specific search form.

There’s a few ways I approach this, depending on the type of site I’m building (Genesis or custom) and how the form will be added to category archives. This article will provide a general overview of how it’s built and a few different implementations so you can find the solution that best fits your needs.

The tl;dr; version is we’ll add a hidden input field to the search form that stores the category slug, then modify the main query on search results page to filter by that category if provided.

Customizing the search form

Most themes will include a searchform.php file which provides the markup for the search form throughout the site. Here’s the searchform.php file from my starter theme:

<?php
/**
 * Search form
 *
 * @package      EAStarter
 * @author       Bill Erickson
 * @since        1.0.0
 * @license      GPL-2.0+
**/
?>

<form role="search" method="get" class="search-form" action="<?php echo esc_url( home_url( '/' ) ); ?>">
	<label>
		<span class="screen-reader-text">Search for</span>
		<input type="search" class="search-field" placeholder="Search…" value="<?php echo get_search_query(); ?>" name="s" title="Search for" />
	</label>
	<button type="submit" class="search-submit"><?php echo ea_icon( array( 'icon' => 'search', 'title' => 'Submit' ) );?></button>
</form>

We’re going to make two changes to the form:

  1. Add a hidden input field containing the category slug we’d like to search.
  2. Make the placeholder text customizable

A simple option would be to check if we’re on a category archive to make our changes. But this would affect all search forms on the site. I only want to affect a specific search form on the page, not other search forms that might be in the site header, sidebar, or footer.

I’m going to check for a global variable, $be_search_form_args, which can be used to customize the search form on a per-form basis. You set up the global variable, call get_search_form() , then reset the global variable.

My new searchform.php file looks like this:

<?php
/**
 * Search form
 *
 * @package      EAStarter
 * @author       Bill Erickson
 * @since        1.0.0
 * @license      GPL-2.0+
**/

global $be_search_form_args;
if( empty( $be_search_form_args ) )
	$be_search_form_args = array();

$search_form_args = shortcode_atts(
	array(
		'placeholder' => 'Search the site …',
		'category' => false,
	),
	$be_search_form_args
);

?>

<form role="search" method="get" class="search-form" action="<?php echo esc_url( home_url( '/' ) ); ?>">
	<label>
		<span class="screen-reader-text">Search for</span>
		<input type="search" class="search-field" placeholder="<?php echo esc_attr( $search_form_args['placeholder'] ); ?>" value="<?php echo get_search_query(); ?>" name="s" title="Search for" />
		<?php
		if( !empty( $search_form_args['category'] ) ) {
			echo '<input type="hidden" name="be_search_cat" value="' . esc_attr( $search_form_args['category'] ) . '" />';
		}
		?>
	</label>
	<button type="submit" class="search-submit"><?php echo ea_icon( array( 'icon' => 'search', 'title' => 'Submit' ) );?></button>
</form>

If I want to display a search form limited to Appetizers, I’ll use:

global $be_search_form_args;
$be_search_form_args = array( 
	'category' => 'appetizers', 
	'placeholder' => 'Search appetizers…'
);
get_search_form();
$be_search_form_args = array();

If someone searches that form for “hummus”, the resulting URL will be
site.com/?s=hummus&be_search_cat=appetizers

Customizing the search query

Now that we’re passing the category we want in the URL, we need to customize the search results query to look for it. We’ll use pre_get_posts.

/**
 * Search query
 * @link https://www.billerickson.net/category-specific-search-form/
 */
function be_search_query( $query ) {
	if( $query->is_main_query() && ! is_admin() && $query->is_search() ) {
		if( !empty( $_GET['be_search_cat'] ) )
			$query->set( 'category_name', esc_attr( $_GET['be_search_cat'] ) );
	}
}
add_action( 'pre_get_posts', 'be_search_query' );

For more information, see my article on Customizing the Main Query.

Using searchform.php with Genesis

If your site was built with the Genesis theme framework, you probably don’t have a searchform.php file in your child theme. The search form itself is defined in the Genesis parent theme, and can be customized using the genesis_search_form filter.

You could use that filter to customize the form markup directly. I find it simpler to create a searchform.php file in my child theme, then use the filter to tell Genesis to use it. That’s what I do in my Genesis starter theme.

First create a searchform.php file in your child theme, using the markup above or any other search form markup you’d like.

Then add the following to your theme’s functions.php file:

/**
 * Custom search form
 *
 */
function be_search_form() {
	ob_start();
	get_template_part( 'searchform' );
	return ob_get_clean();
}
add_filter( 'genesis_search_form', 'be_search_form' );

Automatically add search form to archive pages

Now that you have customized the search form and the query on search results, it’s time to add your form to the site.

We’ll create a function, be_category_search_form(), that only runs on category archive pages and automatically pulls in the relevant information.

/**
 * Category search form 
 * @link https://www.billerickson.net/category-specific-search-form/
 *
 */
function be_category_search_form() {
	if( ! is_category() )
		return;

	global $be_search_form_args;
	$be_search_form_args = array(
		'category' => get_query_var( 'category_name' ),
		'placeholder' => 'Search this category…',
	);
	get_search_form();
	$be_search_form_args = array();
}

How you add this function to your category archive page will depend upon your theme. If you’re using a Genesis theme, you can use a hook:

add_action( 'genesis_before_loop', 'be_category_search_form' );

If your theme has a category.php or archive.php file, you can place it in there.

Category search shortcode

Another way you could use this is as a shortcode, so you could display the search form on a few specific categories.

Most themes will output the category description as introductory text, or have their own “Introductory Text” field when editing a category. Assuming your theme allows shortcodes in this field, add [be_category_search] into it for a category.

Then add the following code to your theme’s functions.php file or a core functionality plugin:

/**
 * Category search form shortcode 
 * @see https://www.billerickson.net/category-specific-search-form/
 *
 */
function be_category_search_shortcode( $atts = array() ) {
	$atts = shortcode_atts( 
		array(
			'category' => false,
			'placeholder' => false,
		), 
		$atts, 
		'be_category_search'
	);

	global $be_search_form_args;
	$be_search_form_args = $atts;
	get_search_form();
	$be_search_form_args = array();

}
add_shortcode( 'be_category_search', 'be_category_search_shortcode' );

Category search block

When possible, I build category landing pages as a block area so we can use Gutenberg blocks on them. I’ll then build my category search block using Advanced Custom Fields. For more information, see Building a block with ACF.

I’ll register my custom block:

acf_register_block_type( array(
	'name'				=> 'category-search',
	'title'				=> __( 'Category Search', 'clientname' ),
	'render_template'		=> 'partials/block-category-search.php',
	'category'			=> 'widgets',
	'icon'				=> 'search',
	'mode'				=> 'preview',
));

I’ll create an ACF field group for the block that lets the user select the category and specify the placeholder text.

I’ll also create the render template file, block-category-search.php, for the block:

<?php
/**
 * Category Search block
 *
 * @package      Client
 * @author       Bill Erickson
 * @since        1.0.0
 * @license      GPL-2.0+
**/

global $be_search_form_args;
$be_search_form_args = array(
	'category' => get_field( 'category' ),
	'placeholder' => get_field( 'placeholder' ),
);
get_search_form();
$be_search_form_args = array();

Category Search ACF Module

If the category landing page is too complex to build using the Gutenberg block editor, I’ll use ACF flexible content.

One of the layouts will be category_landing and have fields for category and placeholder.

I’ll have the following in my ea_module() function:

case 'category_landing':
	global $be_search_form_args;
	$be_search_form_args = array(
		'category' => $module['category'],
		'placeholder' => $module['placeholder'],
	);
	get_search_form();
	$be_search_form_args = array();
	break;

For more information, see Landing Pages with ACF Flexible Content.

Bill Erickson

Bill Erickson is the co-founder and lead developer at CultivateWP, a WordPress agency focusing on high performance sites for web publishers.

About Me
Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions

Comments are closed. Continue the conversation with me on Twitter: @billerickson

Comments

  1. Fabian Todt says

    Couldn’t you just name the hidden input “category_name” (or “category” and set the value to the id) and the correct query var will be set & parsed automatically?

    • Bill Erickson says

      I ran into some issues using category_name for the slug or cat for the ID. It’s been a few months so I don’t remember the exact details, but I think the category parameter triggered is_category to be true in $wp_query, loading the category archive page rather than the search results page. It still did a category-based search but it wasn’t obvious you were viewing a reduced set of posts on the category archive page.

      By using your own custom input and modifying the query with pre_get_posts you don’t have to worry about WordPress getting confused about whether this is a search results page or a category archive.