Sometimes finding content on a website can be hard. As a WordPress Admin, the easier it is to find the content you’re after the better. Thankfully, WordPress provides a search function on most of the Admin screens you’ll use (for example the Posts screen). Whilst this search function is good, it it nonetheless limited in a number of ways.
For example, if you search for a post in the Posts admin screen, the search function will, by default, look for the search query text in the post title, post excerpt, and post content and then will return those results. But, what if you want to search with keywords that are related to a custom field? This information isn’t included in the admin post list table search results which can be frustrating.
In a previous article we learned how to add a custom field in our post types. Let’s take a look at how we can include a custom field in the search results as well.
Extend the Search Query in the Admin Screen Search
In our worked example on custom fields, we created a custom field called ‘source’ in one of our Posts and inserted a URL value.
If you go in the Posts list table and use one of these ‘source’ values in the search bar you won’t get any results as WordPress simply doesn’t look at custom fields when searching by default.
In order to change this, we’ll need to extend the original hook that WordPress provides for the search feature. The hook we have to extend is the pre_get_posts
that is included in the wp-includes/class-wp-query.php
file around line 1779 inside the get_posts
function.
The WordPress Query
Every time you visit a page, either in the front view or admin area, a chain of code scripts execute in order to create the WP_Query
Object corresponding to your request, along with its properties.
Let’s create a temporary view of some of the query’s properties output in order to understand what changes occur in the WP_Query
object in every case.
To view the query before it is executed, we will append the print in the pre_get_posts
action hook. The pre_get_posts
filter fires after the query variable object is created, but before the actual query is run.
In your active theme’s functions.php
file insert this piece of code:
add_action( 'pre_get_posts', 'print_query' );
function print_query( ) {
print_r ( $GLOBALS['wp_query']->query );
echo "<hr>";
print_r ( $GLOBALS['wp_query']->query_vars );
echo "<hr>";
}
Now, if you visit the Posts admin screen, the query
and query_var
s outputs will be printed at the top.
To view the code more easily you can copy and paste it into a free online PHP beautifier.
The $GLOBALS['wp_query']->query
array (first out) will look like this:
Array (
[order] =>
[orderby] =>
[post_type] => post
[posts_per_page] => 20
[post_status] =>
[perm] =>
)
In the case of a search in the Posts admin screen for example, the array output will change including the search string. If we search for ‘wordpress.org’ for example, it will look like this:
Array (
[m] => 0
[cat] => 0
[s] => wordpress.org
[paged] => 1
[order] =>
[orderby] =>
[post_type] => post
[posts_per_page] => 20
[post_status] =>
[perm] =>
)
Assemble the Code
With the above information in mind we will insert the correct code to achieve the search function we require. To do this, head back to your active theme’s functions.php
and delete the code you inserted a few moments ago in the previous step. Then insert the code below:
add_action( 'pre_get_posts', 'extend_admin_search' );
function extend_admin_search( $query ) {
$post_type = 'post';
$custom_fields = array("source",);
if( ! is_admin() )
return;
if ( $query->query['post_type'] != $post_type )
return;
$search_term = $query->query_vars['s'];
$query->query_vars['s'] = '';
if ( $search_term != '' ) {
$meta_query = array( 'relation' => 'OR' );
foreach( $custom_fields as $custom_field ) {
array_push( $meta_query, array(
'key' => $custom_field,
'value' => $search_term,
'compare' => 'LIKE'
));
}
$query->set( 'meta_query', $meta_query );
};
}
Next step is to change the $post_type
and $custom_fields
variables to values that apply to your requirements. Let’s see what we did in the code.
$post_type = 'post';
– Here we define the post type we want to search.$custom_fields = array("source",);
– Here we define the custom field(s) we want to search.
Because we are working with the pre_get_posts
hook, it is important to make sure in our code that we apply our custom search extension while we are on the correct page. Τo tell WordPress to display our code under specific conditions, we use some conditional tags.
- The
if ( ! is_admin() ) return;
condition ensures that if we are not in the admin area, none of our code will execute. - The
if ( $query->query['post_type'] != $post_type )
checks whether we work on the post type we have defined in the$post_type
variable. In our case,post
. - The
$search_term = $query->query_vars['s'];
is where we keep the search string in a variable and then we set it to empty again$query->query_vars['s'] = '';
otherwise it won’t find anything.
Now, with these updates in place, if you refresh the last search you will get the correct results. In our case, we see the ‘Post 1’ post in the results which includes a ‘source’ custom field entry: ‘https://dev.wordpress.org/reference/functions/add_post_meta/’.
Extend the Search Query in the Front View Search
Now we’ve ‘fixed’ our Admin search we can move on to improving our front-end search function so our website users can also search for custom fields.
Try our Award-Winning WordPress Hosting today!
In the same way that previously the Admin search would yield no results for custom fields, the front end search also fails to return any results for custom fields.
Ideally we want users to be able to search for these fields. In this example, we have a custom ‘source’ field in one of our posts that currently contains the url ‘https://dev.wordpress.org/reference/functions/add_post_meta/’. If a user searches for ‘wordpress.org’ then this post should show up in the results. Let’s find out how to do that.
The Code Required
Head to your functions.php file again and copy the code you pasted in earlier. Now, paste this directly below that code (so you have two copies of this code in functions.php
) Now, change the name of the function in this second lot of code to something like extend_front_search
. It should look like this:
add_action( 'pre_get_posts', 'extend_admin_search' );
function extend_admin_search( $query ) {
$post_type = 'post';
$custom_fields = array("source",);
if( ! is_admin() )
return;
if ( $query->query['post_type'] != $post_type )
return;
$search_term = $query->query_vars['s'];
$query->query_vars['s'] = '';
if ( $search_term != '' ) {
$meta_query = array( 'relation' => 'OR' );
foreach( $custom_fields as $custom_field ) {
array_push( $meta_query, array(
'key' => $custom_field,
'value' => $search_term,
'compare' => 'LIKE'
));
}
$query->set( 'meta_query', $meta_query );
};
}
Next, we need to remove the if ( $query->query['post_type'] != $post_type ) return;
condition.
add_action( 'pre_get_posts', 'extend_front_search' );
function extend_front_search( $query ) {
$post_type = 'post';
$custom_fields = array("source",);
if( is_admin() )
return;
$search_term = $query->query_vars['s'];
$query->query_vars['s'] = '';
if ( $search_term != '' ) {
$meta_query = array( 'relation' => 'OR' );
foreach( $custom_fields as $custom_field ) {
array_push( $meta_query, array(
'key' => $custom_field,
'value' => $search_term,
'compare' => 'LIKE'
));
}
$query->set( 'meta_query', $meta_query );
};
}
We just need to maintain the condition if( is_admin() ) return;
to ensure the code will apply only in the front end and we are good to go.
An Alternative Strategy
Another way to extend WordPress search to include custom fields is by altering the SQL query.
The WP_Query
class helps you with that by breaking it into pieces as described around line 2897 of the wp-includes/class-wp-query.php
file.
$clauses = (array) apply_filters_ref_array( 'posts_clauses_request', array( compact( $pieces ), &$this ) );
$where = isset( $clauses['where'] ) ? $clauses['where'] : '';
$groupby = isset( $clauses['groupby'] ) ? $clauses['groupby'] : '';
$join = isset( $clauses['join'] ) ? $clauses['join'] : '';
$orderby = isset( $clauses['orderby'] ) ? $clauses['orderby'] : '';
$distinct = isset( $clauses['distinct'] ) ? $clauses['distinct'] : '';
$fields = isset( $clauses['fields'] ) ? $clauses['fields'] : '';
$limits = isset( $clauses['limits'] ) ? $clauses['limits'] : '';
As we have mentioned before in the custom fields articles, all the custom field data is stored in the database in the ‘postmeta’ table which is not included in the WordPress search by default. So our first step is to join these two tables by using the code below:
function join_meta_table( $join ) {
global $wpdb;
if ( is_search() ) {
$join .=' LEFT JOIN '.$wpdb->postmeta. ' ON '. $wpdb->posts . '.ID = ' . $wpdb->postmeta . '.post_id ';
}
return $join;
}
add_filter('posts_join', 'join_meta_table' );
We used the posts_join
hook of course as it filters the JOIN clause of the query.
Next, we will use the posts_where
hook to alter the WHERE piece of the query to meet our needs.
function modify_where_clause( $where ) {
global $pagenow, $wpdb;
if ( is_search() ) {
$where = preg_replace(
"/\(\s*".$wpdb->posts.".post_title\s+LIKE\s*(\'[^\']+\')\s*\)/",
"(".$wpdb->posts.".post_title LIKE $1) OR (".$wpdb->postmeta.".meta_value LIKE $1)", $where );
}
return $where;
}
add_filter( 'posts_where', 'modify_where_clause' );
Last but not least we need to prevent duplicates. To achieve this we use the posts_distinct
hook which filters the DISTINCT clause of the query.
function prevent_duplicates( $where ) {
global $wpdb;
if ( is_search() ) {
return "DISTINCT";
}
return $where;
}
add_filter( 'posts_distinct', 'prevent_duplicates' );
Now let’s see what happens in the frontend if we search for the “wordpress.org” string.
As expected, the results are correct this time. WordPress search found “post 1” as a result as it contains the ‘source’ field with value ‘https://dev.wordpress.org/reference/functions/add_post_meta/
Custom Post Types in WordPress Search Results
Another thing you might want to do is to make Custom Post Types searchable (if you’re not sure how to create a custom post type then make sure you check out our articles on this here).
Let’s imagine we have created a custom post type named ‘books’ and we want this post type to show up in search results. This time we don’t have to recreate the SQL query, but only re-define the post types in the pre_get_posts
hook as shown described below. The code has to be placed once again in the functions.php
file.
function tg_include_custom_post_types_in_search_results( $query ) {
if ( $query->is_main_query() && $query->is_search() && ! is_admin() ) {
$query->set( 'post_type', array( 'post', 'books' ) );
}
}
add_action( 'pre_get_posts', 'tg_include_custom_post_types_in_search_results' );
What we did here was to add the desired post types to be included in the search results as arguments to the post_type
hook.
If you wish to exclude a type, just remove it from the array.
Conclusion
Improving the search on your WordPress website can be really helpful and a huge time saver. For your end user it’s also important that they can search for both custom post types and also custom fields. Hopefully the instruction above have given you an insight into how to achieve this on your own website.
Start Your 14 Day Free Trial
Try our award winning WordPress Hosting!