Custom WordPress Search With WP_Query

Custom WordPress Search With WP_Query

Online by J&S Code
24 July 2017 | MySQL PHP WP snippets, code & hacks | 1834 recognitions

The WordPress search function is much maligned and there are numerous plugins available to add enhancements but they don’t always provide what you want, especially if you are trying to build a secondary search engine that has specific requirements.

Despite the numerous plugins that will enhance the WordPress search from ordering by relevancy to adding facet searching to including custom fields in the search, there are occasions when any combination of plugins won’t do exactly what you want and you are left with no option but to get your hands dirty and write your own.

In this article, I will walk you through a custom search case study. You’ll find that it’s not actually that difficult to build-your-own search page and you’ll discover some hidden, perhaps, features of the built-in search function.

But before I do that, let’s take a look at what you can achieve simply by putting your own search form together.

Custom WordPress Search With WP_Query

User Requests

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 Overview. 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

The Codex states:

An array of query vars are available for WordPress users or developers to utilize 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

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.

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 vars) .

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:

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

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:

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

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 Class, 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 Loop, thanks to the many methods and properties of the class.

Let’s take a generic Loop as an example:

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:

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 parameters, grouping them in seventeen categories. So, for instance, we have Author Params, Category Params, just one Search parameter (s), Custom Field params, 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 properties. 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:

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

We said that the WP_Query class keeps an array of parameters 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 js, you just need to set the following query:

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:

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

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

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

And so on with tags and taxonomies, search keywords, posts, pages and post types. Refer to the Codex for a more detailed walk-through 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:

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 parameters 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

The Codex informs us that when dealing with a meta query, WP_Query uses the WP_Meta_Query class. 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:

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

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

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:

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:

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

The pre_get_posts action hook 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:

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 use.

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.

Building The Search System

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

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: department;
  • custom taxonomy: typology (B&B, homestay, hotel, etc.)
  • custom field: _js_department_type (entire house, private room, shared room)
  • custom field: _js_department_location
  • other custom fields

We have to register the post type, the custom taxonomy and custom fields and meta boxes.

  1. Register The Query Variables

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:

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:

3. Manipulate The Query

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

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 department.

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). 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_Query and WP_Meta_Query ).

4. Build The Search Form

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 (location 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:

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.

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

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

The Loop won’t show any department, but will add elements to the $locations array from the corresponding custom field value. The condition will skip duplicates. If no departments 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.

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 tag. Here follows the second block of code, generating a new select field corresponding to the typology taxonomy:

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.

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

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

Finally, we can print out the form:

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.

A Free Text Search

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 department by custom keywords. We can do that thanks to the s query argument. So, let’s change the form as follows:

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 .

Wrap up

That said, coding your own search function is not that difficult and gives you access to an even greater range of parameters to control the search behavior.

Have you come across a situation where you need a customized search? Did you find a plugin to help you or did you build your own?

Now it’s time to code!


About The Author

J&S Code

Sorin C is an entrepreneur, online marketer, and an employee of an IT company. When not building websites, creating content, or helping customers improve their online business, spend time with their wife and two beautiful children. Although he still feels new in WordPress, he enjoys sharing what he has learned with all of you! If you want to get in touch with him, you can do this through this website.

Put here your thoughts

On the same idea

Securing WordPress AJAX Forms Using Nonce
Online by stevenmedia | 7 August 2018

When creating a WordPress theme or plugin, AJAX is often used in order to enhance the user experience. In order to ensure security and protect your site against several types...

AJAX for WordPress
Online by stevenmedia | 7 August 2018

This tutorial is just a simple guide to understand the basic about using AJAX in WordPress. (more…)

Customizing the WordPress Query
Online by stevenmedia | 6 August 2018

One of the most powerful features of WordPress is the WP Query. It is what determines what content is displayed on what page. And often you’ll want to modify this...