Tutorials UPDATED: 14 October 2022

WordPress Admin Search: Extending the Results

Tassos Antoniou

11 min read
Image for WordPress Admin Search: Extending the Results

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.

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_vars 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/’.

improve the wordpress admin 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.

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.

wordpress search on the front end

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.

Host your WordPress Website with Pressidium!

View our price plans

OUR READERS ALSO VIEWED:

Building a CI/CD Workflow – Automatically Deploying a WordPress Theme with GitHub Actions

Streamline your WordPress deployment process using GitHub Actions and a CI/CD workflow. Automatically build and deploy a WordPress theme to your Pressidium WordPress site.
Konstantinos Pappas
Konstantinos Pappas
22 min read

Types of Cross-Site Scripting (XSS) Attacks

In this article, on XSS attacks we're going to deep dive cross-site scripting examples to better understand how these types of attacks work.
Tassos Antoniou
Tassos Antoniou
6 min read

5 Best Tips For Web Developers When Coding For eCommerce Websites

So how can you become a successful web developer when coding for ecommerce websites? Check out this article to find out!
Daryl Bush
Daryl Bush
7 min read

WordPress and Cross-Site Scripting (XSS)

Cross-site scripting (XSS) attacks are a common types of website attack seen across the internet. Find out how to protect your website!
Tassos Antoniou
Tassos Antoniou
7 min read
SUBSCRIBE