Menu Search
Jump to the content X X
Smashing Conf New York

We use ad-blockers as well, you know. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. upcoming SmashingConf Barcelona, dedicated to smart front-end techniques and design patterns.

Building An Advanced WordPress Search With WP_Query

Many WordPress superpowers come from its flexible data architecture that allows developers to widely customize their installations with custom post types, taxonomies and fields. However, when it comes down to its search, WordPress provides us with only one-field form that often appears inadequate and leads site admins to adopt external search systems, like Google Custom Search, or third-party plugins.

In this article I’ll show you how to provide your WordPress installation with an advanced search system allowing the user to search and retrieve content from a specific custom post type, filtering results by custom taxonomy terms and multiple custom field values.

The article has two parts. First, I will present a theoretical introduction to handling user requests, starting from the URL transmission, passing through the query execution, and ending with the output production. The second part of the article is a concrete application of what we’re going to learn in the first part, and there we will build our advanced search system.

So, let’s start learning some key concepts.

User Requests Link

When a user clicks on a link or types a URL pointing to a page of the website, WordPress performs a series of operations described well in the Codex Query Overview361. Briefly, this is what happens:

  1. WordPress parses the requested URL into a set of query parameters (called query specification).
  2. All the is_ variables related to the query are set.
  3. The query specification is converted into a MySQL query, which is executed against the database.
  4. The retrieved dataset is stored in the $wp_query object.
  5. WordPress then handles 404 errors.
  6. WordPress sends blog HTTP headers.
  7. The Loop variables are initialized.
  8. The template file is selected according to the template hierarchy rules.
  9. WordPress runs the Loop.

URL parsing comes first, so let’s dive into query strings and variables.

WP Query Vars: Defaults And Custom Variables Link

The Codex2 states:

An array of query vars are available for WordPress users or developers to utilise in order to query for particular types of content or to aid in theme and/or plugin design and functionality.

In other words, WordPress query vars are those variables in a query string that determine (or affect) results in a query performed against the database. By default, WordPress provides public and private query vars, and the Codex defines them as follows:

Public query vars are those available and usable via a direct URL query in the form of

example.net/?var1=value1&var2=value2

Private query vars cannot be used in the URL, although WordPress will accept a query string with private query vars, the values will not be passed into the query and should be placed directly into the query. An example is given below.

query_posts('privatevar=myvalue');

As a consequence, it’s not possible to send via a query string private vars like category__in, category__not_in, category__end, etc. (check the Codex for a comprehensive list of the built-in query vars3) .

With the public variables at our disposal (as users as well as developers), we can assemble a great number of queries with no need to develop a plugin or edit the theme’s functions file. We just need to build a URL, add to the query string one or more of the available parameters, and WordPress will show the requested results to the user.

As an example, we can query for a specific post type by adding the post_type parameter to the query string; or we can request a custom taxonomy, appending to the query string the pair taxonomy-name=taxonomy-term. For instance, we can build the following URL:

mywebsite.com/?post_type=movie&genre=thriller

WordPress will query the database and retrieve all movie post types belonging to the thriller genre, where genre is a custom taxonomy.

It’s awesome, but that’s not all. What we have said so far, in fact, concerns just the built-in functionalities of query vars. WordPress allows us to go further and create our own custom query variables.

Register Custom Query Vars Link

Before we can use them, the custom query vars should be registered. We can accomplish this task thanks to the query_vars filter. So, let’s open the main file of a plugin (or a theme’s functions.php file) and write the following code:

/**
 * Register custom query vars
 *
 * @link https://codex.wordpress.org/Plugin_API/Filter_Reference/query_vars
 */
function myplugin_register_query_vars( $vars ) {
	$vars[] = 'author';
	$vars[] = 'editor';
	return $vars;
}
add_filter( 'query_vars', 'myplugin_register_query_vars' );

The callback function keeps an array of variables as an argument, and must return the same array when new variables have been added

Now we can include the new variables in the parameters that will affect the query. These parameters will be available in our scripts thanks to the get_query_var() template tag, as we’ll see later. Now it’s time to introduce the class that manages the queries in WordPress.

Her Majesty The WP_Query Link

Querying a database is not an easy task. It’s not only a matter of building an efficient query, but it’s a problem that requires security issues to be carefully taken into account. Thanks to WP_Query Class4, WordPress gives us access to the database quickly (no need to get our hands dirty with SQL) and securely (WP_Query builds safe queries behind the scenes).

The retrieved dataset will be available for use in the Loop5, thanks to the many methods and properties of the class.

Let’s take a generic Loop as an example:

<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
	<!-- code here -->
<?php endwhile; else : ?>
	<!-- code here -->
<?php endif; ?>

If you are new to WordPress development, you may ask: “Hey, buddy! Where’s the Query?”

In fact, you don’t need to create a new instance of the WP_Query object. The class itself establishes the query to be executed according to the requested page. So, if the site viewer requires a category archive, WordPress will run a query retrieving all posts belonging to that specific category, and the Loop will show them.

But this is just a vary basic example of a main query. We can do a lot of more, and filter the returning result set granularly, just by passing an array of parameters to a new instance of the WP_Query class, as we’ll do in the following example:

// An array of arguments
$args = array( 'arg_1' => 'val_1', 'arg_2' => 'val_2' );

// The Query
$the_query = new WP_Query( $args );

// The Loop
if ( $the_query->have_posts() ) {
	
	while ( $the_query->have_posts() ) : $the_query->the_post(); 
		// Your code here
	endwhile;
	
} else {
        // no posts found
}
/* Restore original Post Data */
wp_reset_postdata();

Things look a bit more complicated, don’t they? But if we look closely, they’re not.

The new instance of WP_Query keeps an array of arguments that will affect data retrieved from the database. The Codex provides the full list of parameters6, grouping them in seventeen categories. So, for instance, we have Author Params7, Category Params8, just one Search parameter9 (s), Custom Field params10, and so on (we’ll get back to WP_Query params in a moment).

Now that we have instantiated the $query object, we can access all its methods and properties11. have_posts checks whether any post remains to be printed, while the_post moves the Loop forward to the succeeding post and updates the $post global variable.

Outside the Loop, when using a custom query, we should always make a call to wp_reset_postdata(). This function restores the $post global variable after the execution of a custom query, and is necessary because any new query overwrites $post. From the Codex:

Note: If you use the_post() with your query, you need to run wp_reset_postdata() afterwards to have Template Tags use the main query’s current post again.

Now let’s get back to the query args.

WP_Query Arguments Link

We said that the WP_Query class keeps an array of parameters12 that allow developers to select granularly the results from the database.

The first group, the Author Parameters, includes those arguments that allow us to build queries based on the author(s) of the posts (pages and post types). They include:

  • author
  • author_name
  • author__in
  • author__not_in

If you want to retrieve all the posts from carlo, you just need to set the following query:

$query = new WP_Query( array( 'author_name' => 'carlo' ) );

The second group includes the Category Parameters, i.e. all those arguments that allow us to query for (or exclude) posts assigned to one or more categories:

  • cat
  • category_name
  • category__and
  • category__in
  • category__not_in

If we needed all posts assigned to the webdesign category, we just have to set the category_name argument, as we’ll do in the following example:

$query = new WP_Query( array( 'category_name' => 'webdesign' ) );

The following query searches for posts from more than one category, the comma standing in for OR:

$query = new WP_Query( array( 'category_name' => 'webdesign,webdev' ) );

We can also ask for posts belonging to both webdesign and webdev categories, with plus (+) meaning AND:

$query = new WP_Query( array( 'category_name' => 'webdesign+webdev' ) );

And we can also pass an array of IDs, as in the next examples:

$query = new WP_Query( array( 'category__in' => array( 4, 9 ) ) );
$query = new WP_Query( array( 'category__and' => array( 4, 9 ) ) );

And so on with tags and taxonomies, search keywords, posts, pages and post types. Refer to the Codex for a more detailed walk-through13 of query arguments.

As we said before, we can also set more than one argument and retrieve, for instance, all posts in a specific category AND written by a certain author:

$query = new WP_Query( array( 'author_name' => 'carlo', 'category_name' => 'webdesign' ) );

When the data architecture get more complex – and that occurs when we add custom fields and taxonomies to post types – then it could become necessary to set one or more Custom Field parameters14 allowing us to retrieve all posts (or custom post types) labeled with specific custom field values. Shortly, we’ll need to execute a meta query against the database.

WordPress Meta Queries Link

The Codex informs us that when dealing with a meta query, WP_Query uses the WP_Meta_Query class15. This class, introduced in WordPress 3.2, builds the SQL code of the queries based on custom fields.

To build a query based on a single custom field, we just need one or more of the following arguments:

  • meta_key
  • meta_value
  • meta_value_num
  • meta_compare

Suppose a custom post type is named accommodation. Let’s assign to each accommodation a custom field named city, which stores the name of a geographical location. With a meta query we can retrieve from the database all accommodations located in the specified city, simply passing the right arguments to the query, as you can see below:

$args = array( 
	'post_type'		=> 'accommodation', 
	'meta_key'		=> 'city', 
	'meta_value'		=> 'Freiburg', 
	'meta_compare'	=> 'LIKE' );

Once we’ve set the arguments, we can build the query the same way as before:

// The Query
$the_query = new WP_Query( $args );

// The Loop
if ( $the_query->have_posts() ) {
	
	while ( $the_query->have_posts() ) : $the_query->the_post(); 
		// Your code here
	endwhile;
	
} else {
        // no posts found
}
/* Restore original Post Data */
wp_reset_postdata();

Copy and paste the code above in a template file and you’ll get an archive of all accommodations available in Freiburg.

This is the case for a single custom field. But what if we needed to build a query based on multiple custom fields?

The meta_query Argument Link

For this kind of query, the WP_Meta_Query class (and the WP_Query class as well) provides the meta_query parameter. This has to be an array of arrays, as shown in the following example:

$args = array(
	'post_type'  => 'accommodation',
	'meta_query' => array(
		array(
			'key'     => 'city',
			'value'   => 'Freiburg',
			'compare' => 'LIKE',
		),
	)
);

The meta_query element is a bidimensional array whose items are single meta queries with the following arguments:

Argument Type Description
key string Identifies the custom field.
value string|array Can be an array just when the compare value is 'IN', 'NOT IN', 'BETWEEN', or 'NOT BEETWEEN'.
compare string A comparison operator. Accepted values are '=', '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'EXISTS', 'NOT EXISTS', 'REGEXP', 'NOT REGEXP' and 'RLIKE'. It defaults to '='.
type string The custom field type. Possible values are 'NUMERIC', 'BINARY', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', 'UNSIGNED'. It defaults to 'CHAR'.

If we set more than one custom field, we also have to assign the relation argument to the meta_query element.

Now we can build a more advanced query. Let’s begin by setting the arguments and creating a new instance of WP_Query:

$args = array(
	'post_type'	=> 'accommodation',
	'meta_query'	=> array(
		array( 'key' => 'city', 'value' => 'Paris', 'compare' => 'LIKE' ),
		array( 'key' => 'type', 'value' => 'room', 'compare' => 'LIKE' ),
		'relation' => 'AND'
	)
);
$the_query = new WP_Query( $args );

Here, the meta_query argument holds two meta query arrays and a third parameter setting the relation between the meta queries. The query searches the wp_posts table for all accommodation post types where the custom fields city and type store respectively the values Paris and room.

Let’s copy and paste the code into a template file named archive-accommodation.php. When requested, WordPress will execute the query searching the wp_posts table, and the Loop will show the results, if available.

At this point we’re still writing the code in a template file. This means that our script is static and each time the Loop runs, it will produce the same output. But we need to allow site users to make custom requests, and to accomplish this task we need to dynamically build custom queries.

The pre_get_posts Filter Link

The pre_get_posts action hook16 is fired after the $query object creation, but before its execution. To modify the query, we’ll have to hook a custom callback to pre_get_posts.

In an earlier example, we queried the database to retrieve all posts in the webdesign category from a certain author. In the following example we’re passing the same arguments to the $query object, but we won’t do the job with a template file, as we’ve done before, but we’ll use the main file of a plugin (or a theme’s functions.php), instead. Let’s write the following block of code:

function myplugin_pre_get_posts( $query ) {
	// check if the user is requesting an admin page 
	// or current query is not the main query
	if ( is_admin() || ! $query->is_main_query() ){
		return;
	}
	$query->set( 'author_name', 'carlo' );
	$query->set( 'category_name', 'webdesign' );
}
add_action( 'pre_get_posts', 'myplugin_pre_get_posts', 1 );

The $query object is passed to the callback function by reference, not by value, and this means that any changes made to the query directly affect the original $query object. The Codex says:

The pre_get_posts action gives developers access to the $query object by reference (any changes you make to $query are made directly to the original object – no return value is necessary).

As we’re manipulating the original $query object, we have to pay attention to which query we’re working on. The is_main_query method checks if the current $query object is… (yes!) the main query. The Codex also informs us that the pre_get_posts filter can affect the admin panel as well as the front-end pages. For this reason, it’s more than appropriate to check the requested page with the is_admin conditional tag as well.

The pre_get_posts action hook is well documented and it’s well worth reading the Codex for a more detailed description and several examples of use17.

We can now end our introduction to the the main tools available to handle WordPress queries. Now it’s time to present a concrete example of their use, building an advanced search system of the site content. Our case study is provided by a real estate website.

From Theory To Code: Building The Search System Link

We will follow these steps:

  1. Define the data structure.
  2. Register the custom query vars.
  3. Get the query var values and use them to build a custom query.
  4. Build a form programmatically generating the field values.

1. Define The Data Structure Link

The purpose of the custom post types is to add contents that logically can be included neither in blog posts nor in static pages. Custom post types are particularly appropriate to present events, products, books, movies, catalogue items, and so on. Here we’re going to build an archive of real estate ads with the following structure:

  • custom post type: accommodation;
  • custom taxonomy: typology (B&B, homestay, hotel, etc.)
  • custom field: _sm_accommodation_type (entire house, private room, shared room)
  • custom field: _sm_accommodation_city
  • other custom fields

We have to register the post type, the custom taxonomy and custom fields and meta boxes, as shown in the figure below.

Accommodation edit page18
The edit accommodation page shows all custom meta boxes and fields. (View large version19)

The image shows how the Edit Accommodation page will appear once three custom meta boxes containing the Typology custom taxonomy and several custom fields have been registered.

It’s not our goal to analyze WordPress data architecture, as this topic has already been covered here on Smashing Magazine by Daniel20, Brian21, Kevin22, Josh23 and Justin24. Read their articles if you need a refresher, and come back as soon as you’re ready.

Once we’ve defined the data architecture, it’s time to register the query vars.

2. Register The Query Variables Link

Earlier we defined query vars as key=value pairs following a question mark in a URL. But before we can handle these pairs in our scripts, we have to register them in a plugin or functions file. For our purposes, we need just two variables that will enable the execution of a query based on the values of the corresponding custom fields:

/**
 * Register custom query vars
 *
 * @link https://codex.wordpress.org/Plugin_API/Filter_Reference/query_vars
 */
function sm_register_query_vars( $vars ) {
	$vars[] = 'type';
	$vars[] = 'city';
	return $vars;
} 
add_filter( 'query_vars', 'sm_register_query_vars' );

That’s it! We have added two more parameters to query the database. Now it would make sense to build a URL like this one:

http://example.com/?type=XXX&city=YYY

3. Manipulate The Query Link

Now let’s add a new block of code to our script:

/**
 * Build a custom query based on several conditions
 * The pre_get_posts action gives developers access to the $query object by reference
 * any changes you make to $query are made directly to the original object - no return value is requested
 *
 * @link https://codex.wordpress.org/Plugin_API/Action_Reference/pre_get_posts
 *
 */
function sm_pre_get_posts( $query ) {
	// check if the user is requesting an admin page 
	// or current query is not the main query
	if ( is_admin() || ! $query->is_main_query() ){
		return;
	}

	// edit the query only when post type is 'accommodation'
	// if it isn't, return
	if ( !is_post_type_archive( 'accommodation' ) ){
		return;
	}

	$meta_query = array();

	// add meta_query elements
	if( !empty( get_query_var( 'city' ) ) ){
		$meta_query[] = array( 'key' => '_sm_accommodation_city', 'value' => get_query_var( 'city' ), 'compare' => 'LIKE' );
	}

	if( !empty( get_query_var( 'type' ) ) ){
		$meta_query[] = array( 'key' => '_sm_accommodation_type', 'value' => get_query_var( 'type' ), 'compare' => 'LIKE' );
	}

	if( count( $meta_query ) > 1 ){
		$meta_query['relation'] = 'AND';
	}

	if( count( $meta_query ) > 0 ){
		$query->set( 'meta_query', $meta_query );
	}
}
add_action( 'pre_get_posts', 'sm_pre_get_posts', 1 );

This code is the sum of all we covered in the first part of the article. The callback function quits if the user is in the admin panel, if the current query is not the main query, and if the post type is not accommodation.

Later, the function checks if any of the query vars we’ve registered before are available. This task is accomplished thanks to the get_query_var function, which retrieves a public query variable from an HTTP request (read more about get_query_var in the Codex25). If the variable exists, then the callback defines one array for each meta query and pushes it into the multi-dimensional $meta_query array.

Finally, if at least two meta queries are available, then the relation argument is pushed into $meta_query and its value is set to 'AND'. Once done, the set method saves $query for its subsequent execution.

You don’t need to worry about data sanitization here, because the WP_Query and WP_Meta_Query classes do the job for us (check the WP_Query26 and WP_Meta_Query source code27)

4. Build The Search Form Link

The form data are submitted with the GET method. This means that the name and value attributes of the form fields are sent as URL variables (i.e. as query vars). So we’re going to give the name attributes of the form fields the same values as the previously registered query vars (city and type), while the field values could be assigned programmatically, retrieving data from the database, or filled in by the user.

First we will create a shortcode that will allow the site admin to include a search form in posts and pages of the website. Our shortcode will be hooked to the init action:

function sm_setup() {
	add_shortcode( 'sm_search_form', 'sm_search_form' );
}
add_action( 'init', 'sm_setup' );

Next we will define the callback function that will produce the HTML of a form containing three select fields, corresponding to a custom taxonomy and two custom fields.

function sm_search_form( $args ){
	// our code here
}

$args is an array of the shortcode attributes. Inside the function we’ll add the following code:

// The Query
// meta_query expects nested arrays even if you only have one query
$sm_query = new WP_Query( array( 'post_type' => 'accommodation', 'posts_per_page' => '-1', 'meta_query' => array( array( 'key' => '_sm_accommodation_city' ) ) ) );

// The Loop
if ( $sm_query->have_posts() ) {
	$cities = array();
	while ( $sm_query->have_posts() ) {
		$sm_query->the_post();
		$city = get_post_meta( get_the_ID(), '_sm_accommodation_city', true );

		// populate an array of all occurrences (non duplicated)
		if( !in_array( $city, $cities ) ){
			$cities[] = $city;    
		}
	}
}
} else{
       echo 'No accommodations yet!';
       return;
}


/* Restore original Post Data */
wp_reset_postdata();

if( count($cities) == 0){
	return;
}

asort($cities);
    
$select_city = '<select name="city" style="width: 100%">';
$select_city .= '<option value="" selected="selected">' . __( 'Select city', 'smashing_plugin' ) . '</option>';
foreach ($cities as $city ) {
	$select_city .= '<option value="' . $city . '">' . $city . '</option>';
}
$select_city .= '</select>' . "\n";

reset($cities);

We’ve built a query that will retrieve all accommodation post types having set a custom field named _sm_accommodation_city (the underscore preceding the name represents a hidden custom field).

The Loop won’t show any accommodation, but will add elements to the $cities array from the corresponding custom field value. The condition will skip duplicates. If no accommodations are available, the execution is interrupted; otherwise the array elements are sorted and used to print the values of the first group of option elements.

Select box28
All available cities are shown as options in a select box.

The second form field is still a select button, and it corresponds to the typology custom taxonomy. The values of the second group of option elements are provided by the get_terms template tag29. Here follows the second block of code, generating a new select field corresponding to the typology taxonomy:

$args = array( 'hide_empty' => false );
$typology_terms = get_terms( 'typology', $args );
if( is_array( $typology_terms ) ){
	$select_typology = '<select name="typology" style="width: 100%">';
	$select_typology .= '<option value="" selected="selected">' . __( 'Select typology', 'smashing_plugin' ) . '</option>';
	foreach ( $typology_terms as $term ) {
		$select_typology .= '<option value="' . $term->slug . '">' . $term->name . '</option>';
	}
	$select_typology .= '</select>' . "\n";
}

get_terms returns an array of all terms of the taxonomy set as first argument, or a WP_Error object if the taxonomy does not exist. Now again a foreach cycle prints out the option elements.

Select box30
The options of the second select box.

Then we build the last select element, corresponding to the type custom field. Here is the code:

$select_type = '<select name="type" style="width: 100%">';
$select_type .= '<option value="" selected="selected">' . __( 'Select room type', 'smashing_plugin' ) . '</option>';
$select_type .= '<option value="entire">' . __( 'Entire house', 'smashing_plugin' ) . '</option>';
$select_type .= '<option value="private">' . __( 'Private room', 'smashing_plugin' ) . '</option>';
$select_type .= '<option value="shared">' . __( 'Shared room', 'smashing_plugin' ) . '</option>';
$select_type .= '</select>' . "\n";

As you can see, in this case we have manually set the option values.

Finally, we can print out the form:

$output = '<form action="' . esc_url( home_url() ) . '" method="GET" role="search">';
$output .= '<div class="smselectbox">' . esc_html( $select_city ) . '</div>';
$output .= '<div class="smselectbox">' . esc_html( $select_typology ) . '</div>';
$output .= '<div class="smselectbox">' . esc_html( $select_type ) . '</div>';
$output .= '<input type="hidden" name="post_type" value="accommodation" />';
$output .= '<p><input type="submit" value="Go!" class="button" /></p></form>';

return $output;

We’ve set a hidden input field for the post_type public query var. When the user submits the form, WordPress gets the post_type value, and loads the archive.php template file, or, if available, the archive-{post_type}.php file. With this kind of form, if you’re going to customize the HTML structure of the resulting page, you’ll need to provide the most appropriate template file.

Advanced search form31
The image shows the advanced search form we’ve been building through this article.

The form we’ve built so far allows the user to set up three filters from a number of predetermined options. We’re now going to improve the search system including a text field in the form, so that users can search accommodation by custom keywords. We can do that thanks to the s query argument32. So, let’s change the form as follows:

$output = '<form id="smform" action="' . esc_url( home_url() ) . '" method="GET" role="search">';
$output .= '<div class="smtextfield"><input type="text" name="s" placeholder="Search key..." value="' . get_search_query() . '" /></div>';
$output .= '<div class="smselectbox">' . esc_html( $select_city ) . '</div>';
$output .= '<div class="smselectbox">' . esc_html( $select_typology ) . '</div>';
$output .= '<div class="smselectbox">' . esc_html( $select_type ) . '</div>';
$output .= '<input type="hidden" name="post_type" value="accommodation" />';
$output .= '<p><input type="submit" value="Go!" class="button" /></p></form>';

Thanks to the text field, we can pass WP_Query a new key/value pair, where the key is the s parameter, and the value is the user input or the get_search_query() returning value (read the Codex33 for more details).

Advanced search form34
A more advanced search form.

A final note: in our preceding example, we’ve seen WordPress loading an archive template file to show the results of the query. That’s because we did not set the search argument. When the query string contains the s param, WordPress automatically loads the search template file, as shown in the last image of this post.

Advanced search form35
The image shows the search page in Twenty Fifteen.

Conclusions Link

The examples of this article are intended to demonstrate what can be achieved with the tools provided by WordPress. Sure, the form can be improved by adding new fields allowing more granular customization. Nevertheless, I hope I’ve provided a quite detailed view of the functionalities that make it possible to build a search system overcoming the limits of the built-in search system with no need of external services and plugins.

Now it’s time to code!

Resources Link

(og)

Footnotes Link

  1. 1 https://codex.wordpress.org/Query_Overview
  2. 2 https://codex.wordpress.org/WordPress_Query_Vars
  3. 3 https://codex.wordpress.org/WordPress_Query_Vars#Query_variables
  4. 4 http://codex.wordpress.org/Class_Reference/WP_Query
  5. 5 https://codex.wordpress.org/The_Loop
  6. 6 https://codex.wordpress.org/Class_Reference/WP_Query#Parameters
  7. 7 https://codex.wordpress.org/Class_Reference/WP_Query#Author_Parameters
  8. 8 https://codex.wordpress.org/Class_Reference/WP_Query#Category_Parameters
  9. 9 https://codex.wordpress.org/Class_Reference/WP_Query#Search_Parameter
  10. 10 https://codex.wordpress.org/Class_Reference/WP_Query#Custom_Field_Parameters
  11. 11 https://codex.wordpress.org/Class_Reference/WP_Query#Methods_and_Properties
  12. 12 https://codex.wordpress.org/Class_Reference/WP_Query#Parameters
  13. 13 https://codex.wordpress.org/Class_Reference/WP_Query#Parameters
  14. 14 https://codex.wordpress.org/Class_Reference/WP_Query#Custom_Field_Parameters
  15. 15 https://codex.wordpress.org/Class_Reference/WP_Meta_Query
  16. 16 https://codex.wordpress.org/Plugin_API/Action_Reference/pre_get_posts
  17. 17 https://codex.wordpress.org/Plugin_API/Action_Reference/pre_get_posts
  18. 18 https://www.smashingmagazine.com/wp-content/uploads/2016/02/02-new_accommodation-opt.png
  19. 19 https://www.smashingmagazine.com/wp-content/uploads/2016/02/02-new_accommodation-preview-opt.png
  20. 20 http://www.smashingmagazine.com/2012/11/complete-guide-custom-post-types/
  21. 21 http://www.smashingmagazine.com/2015/04/extending-wordpress-custom-content-types/
  22. 22 http://www.smashingmagazine.com/2012/01/create-custom-taxonomies-wordpress/
  23. 23 http://www.smashingmagazine.com/2014/08/customizing-wordpress-archives-categories-terms-taxonomies/
  24. 24 http://www.smashingmagazine.com/2011/10/create-custom-post-meta-boxes-wordpress/
  25. 25 https://codex.wordpress.org/Function_Reference/get_query_var
  26. 26 https://core.trac.wordpress.org/browser/tags/4.4.1/src/wp-includes/query.php
  27. 27 https://core.trac.wordpress.org/browser/tags/4.4.1/src/wp-includes/class-wp-meta-query.php
  28. 28 https://www.smashingmagazine.com/wp-content/uploads/2016/02/03-select_city-preview-opt.png
  29. 29 https://codex.wordpress.org/Function_Reference/get_terms
  30. 30 https://www.smashingmagazine.com/wp-content/uploads/2016/02/04-select_typology-preview-opt.png
  31. 31 https://www.smashingmagazine.com/wp-content/uploads/2016/02/05-wordpress_advanced_search_form-preview-opt.png
  32. 32 https://codex.wordpress.org/Class_Reference/WP_Query#Search_Parameter
  33. 33 https://codex.wordpress.org/Template_Tags/get_search_query
  34. 34 https://www.smashingmagazine.com/wp-content/uploads/2016/02/06-wordpress_advanced_search_form_2-preview-opt.png
  35. 35 https://www.smashingmagazine.com/wp-content/uploads/2016/02/07-ocean-preview-opt.png
  36. 36 https://codex.wordpress.org/Query_Overview
  37. 37 https://codex.wordpress.org/Class_Reference/WP_Query
  38. 38 https://codex.wordpress.org/Class_Reference/WP_Meta_Query
  39. 39 https://codex.wordpress.org/Custom_Queries
  40. 40 http://codex.wordpress.org/WordPress_Query_Vars
SmashingConf New York

Hold on, Tiger! Thank you for reading the article. Did you know that we also publish printed books and run friendly conferences – crafted for pros like you? Like SmashingConf Barcelona, on October 25–26, with smart design patterns and front-end techniques.

↑ Back to top Tweet itShare on Facebook

Carlo is a freelance front-end designer and developer. In the last years he's been writing for ioProgrammo, an Italian printed computer magazine, and HTML.it, a popular design and development Italian website.

When he writes articles and tutorials, Carlo mainly deals with web standards, but when he plays with websites his best workmate is WordPress.

You can follow him on Twitter @carlodaniele.

  1. 1

    Daniel Winnard

    March 29, 2016 10:26 am

    Hi,

    This is exactly what I have been looking for! Great tutorial and super easy to follow for a non-developer like myself.

    I built a business directory WordPress page for a client where they needed this functionality for users to search by two taxonomies, business type and city.

    I never did get this working properly because I didn’t fully understand how to manipulate the query dynamically. This tutorial has saved me hours.

    Many thanks.

    Practical tutorials / articlas like this are why I always come back to Smashing Magazine.

    Thanks

    6
  2. 3

    Pedro Baumann

    March 29, 2016 3:20 pm

    Hello Carlos, great article!

    I wanted to ask, what are the benefits of doing it like this (with get query parameters added to the wp_query functionality, instead of parsing for example $_GET and appending those parameters to a custom WP_Query object dynamically?

    I reckon it’s the same but this is the WP way to do it?

    I’ve done it like that for a similar filter with ajax functionality.

    2
    • 4

      Carlo Daniele

      March 29, 2016 6:41 pm

      Hi Pedro.
      Usually WordPress takes care of data sanitization, but this is not the case. get_query_var does not sanitize data, although WP_Query does the job for us. So, I think you could use $_GET as well, taking care of data parsing (and sanitization).
      Neverthelles, as a general rule I always prefer to make use of WordPress functions when available, unless WordPress does not exactly what I need. Definitely, using WordPress functions can save you hours of coding.

      1
  3. 5

    Konstantin Kovshenin

    April 2, 2016 11:14 am

    Great article Carlo! One thing to add.

    Meta queries are slow. Meta queries with LIKE are even slower. These will almost always cause a full table scan in MySQL, so if you’re dealing with anything other than a very small dataset, you’re going to run into performance trouble. And when you do, it’s often a good idea to look into third party indexing and search tools, such as Elasticsearch and Sphinx. ElasticPress by 10up is a good place to start.

    2
    • 6

      Carlo Daniele

      April 2, 2016 8:33 pm

      Good advice! Thank you for your comment, Konstantin.
      Another useful tool we can use when dealing with meta queries is the Query Monitor plugin. It provides a huge amount of information about queries, like the query itself, the caller, the affected rows and the execution time. We’re warned when a query is slowing down the page loads, so that we can switch to third party services like ElasticPress.

      0
  4. 7

    It looks like there was an issue with greater than signs in your article. They’ve all been converted (especially in the code snippets) to the HTML ASCII codes. That makes the snippets not very easily copy/paste-able. Just wanted to point that out in case you’d missed it!

    1
    • 8

      Carlo Daniele

      April 4, 2016 9:39 pm

      Thank you for your comment, Pete. I’ll let the staff know about this problem

      0
  5. 9

    This is a great tutorial however I am not getting the search results via the url… just lists all of the entries via the post_type= tag thoughts?

    0
  6. 10

    Sean Rasmussen

    July 4, 2016 5:15 pm

    Great tutorial. One thing that would be great to include would be the final code as a complete block. It’s a bit hard to understand how it all fits together without seeing the final code block (s). Thanks!

    2
    • 11

      I will also love to see the final code as a complete block since I find this tutorial hard to follow

      0
  7. 12

    Hi,
    I’ve seen alot of criticism of wp_query that it can’t handle custom posts and multi select taxonomies, that to handle these you need to use third party search/filter plugins, but this tutorial suggests that this is not the case, is this correct?
    I’m using Advanced Custom Fields and CPT-UI plugins can I use this method to search multi select taxonomies?
    Thanks for your help
    David

    0

↑ Back to top