Customize wordpress search results, searching custom fields and related taxonomy terms

This article shows how to customize wordpress search results by exploring the main WordPress search query and extending WP_Query to include extra WordPress search fields such as custom fields and related taxonomy terms. This is accomplished using SQL JOINS on the postmeta and taxonomy database tables into the search query via the posts_join and posts_where filters.

This article contains of the following sections on how to customize wordpress search results:

  1. Introduction to customizing the WordPress search query.
  2. How to limit the number of wordpress search results.
  3. How to limit wordpress search to a specific list of post types.
  4. Customize wordpress search query to Include custom fields.
  5. Include related taxonomy fields in the wordpress seach query.

Customizing the WordPress Search Query

All post, pages and custom post types are accessed via the core WP_Query class, this is the same for the main WordPress search query, all examples of code in this article use the same entrypoint to inject code into this class via the ‘pre_get_posts’ action.

The WordPress ‘pre_get_posts’ action is triggered every time a new WP_Query is created, since we only need to run our code when the main WordPress search query has been triggered we have to conditionaly check if our code should load, this way we know we are only running on the main WordPress query for the search results page.

  1. Only load on the public facing website, e.g. not the admin area using is_admin()
  2. Make sure to run only on a search query using is_search().
  3. Check that we are running on the main query for that page using is_main_query()

If all these conditions are met then we know we are running at the correct location and our code can be injected using the following code, this is used as a base for all other code examples in this article and all code that extends it is added after the TODO comment: where we install our search customization hooks or code.

/**
 * Install search customization hooks in WP_Query pre_get_posts action 
 * 
 * @param \WP_Query $query 
 * @return void 
 */
function jcas_setup($query)
{
    // escape if not the main wordpress search query
    if (is_admin() || !$query->is_search() || !$query->is_main_query()) {
        return;
    }

    // TODO: install our search customization hooks or code here.
}

add_action('pre_get_posts', 'jcas_setup', 9);

Limit number of wordpress search results

Limiting the number of WordPress search results can be achieved simply by changing the WP_Query query variable ‘posts_per_page’ , using the set method of the current query we can either set it to specific number to display paginated results or to -1 to show all search results without pagination.

function jcas_setup($query)
{
    // escape if not the main wordpress search query
    if (is_admin() || !$query->is_search() || !$query->is_main_query()) {
        return;
    }

    // install our search customization hooks or code here.

    $query->set('posts_per_page', -1);
}

Limit wordpress search results to specific post types

By default the main wordpress search query retrives any post types except revisions and types that are defined as excluded from the search. If you want to refine your search by restricting what post types are being queried you can by setting the ‘post_type’ query variable of WP_Query to a string containing the name of a specific post type, or an array of allowed post types.

function jcas_setup($query)
{
    // escape if not the main wordpress search query
    if (is_admin() || !$query->is_search() || !$query->is_main_query()) {
        return;
    }

    // install our search customization hooks or code here.

    $query->set('post_type', ['post', 'page']);
}

Customize WordPress search results to include Custom fields

Adding custom fields into the WordPress search results can be handying for example if you have a list of books and you want your vistors to find them by the ISBN number, or if WordPress website was built using custom fields and you want all the data to be searchable.

By default the main WordPress search query looks at the post_title, post_excerpt and post_content, to include custom fields into the query we must customize the WordPress Seach query to include the postmeta table, this is accomplished using the ‘posts_join’ filter to add an SQL JOIN to include the postmeta table.

With the postmeta table connected we need to extend the SQL query, to do this we can modify the WHERE query using the ‘posts_where’ filter withour own custom logic.

We first capture the search query by using preg_replace which uses a regular expression to find the default post_title LIKE ‘value’ (The regex looks for the search value after post_title that is wrapped inside of single quotes ‘, by using [^\’] to capture all characters that do not match a single quote), then we add use an SQL OR condition to append our custom field search SQL, referencing the previously joined postmeta table and checking to see if the meta_value column matches the captured search query.

Finaly when adding filters such as the ‘posts_join’ and ‘posts_where’ we need to make sure they are removed once they have been called, as you can see at the end of each filters function before the modified result is returned we remove that filter.

function jcas_setup($query)
{
    // escape if not the main wordpress search query
    if (is_admin() || !$query->is_search() || !$query->is_main_query()) {
        return;
    }

    // install our search customization hooks or code here.

    add_filter('posts_join', 'jcas_post_join');
    add_filter('posts_where', 'jcas_post_where');
}

function jcas_post_join($join)
{
    $join .= " LEFT JOIN wp_postmeta AS jcas_pm1 ON (wp_posts.ID = jcas_pm1.post_id) ";

    // uninstall hook
    remove_filter('posts_join', 'jcas_post_join');

    return $join;
}

function jcas_post_where($where)
{
    // implement SQL to search custom fields
    $where = preg_replace(
        "/post_title LIKE (\'[^\']+\')\s*\)/",
        "post_title LIKE $1) 
            OR CAST(jcpm1.meta_value AS CHAR) LIKE $1 ",
        $where
    );

    // uninstall hook
    remove_filter('posts_where', 'jcas_post_where');

    return $where;
}

Search WordPress custom fields by name

To search a specific list of custom fields names we can change they SQL query as shown in the following example, checking to see if the meta_key matches the name of the required custom field, prefixing the meta_key with our previously joined postmeta table.

function jcas_post_where($where)
{
    $where = preg_replace(
        "/post_title LIKE (\'[^\']+\')\s*\)/",
        "post_title LIKE $1) 
            OR (
                jcpm1.meta_key = '_custom_field_1' 
                AND CAST(jcpm1.meta_value AS CHAR) LIKE $1
            )
            OR  (
                jcpm1.meta_key = '_custom_field_2' 
                AND CAST(jcpm1.meta_value AS CHAR) LIKE $1
            ) ",
        $where
    );

    // ...
}

Search WordPress custom fields name with ‘%’ wildcard search

If you have any custom fields that use a row or record index in the name (for example using Advanced custom fields repeater field), then you may run in to the issue where the custom field you are wanting to search has multiple entries each with the current row number in their name (e.g. row_1_name, row_2_name …), we can search this similar to specifying the custom field directly we can use an SQL LIKE and replace the row number with a ‘%’ character.

function jcas_post_where($where)
{
    $where = preg_replace(
        "/post_title LIKE (\'[^\']+\')\s*\)/",
        "post_title LIKE $1) 
            OR (
                jcpm1.meta_key LIKE 'row_%_name' 
                AND CAST(jcpm1.meta_value AS CHAR) LIKE $1
            ) ",
        $where
    );

    // ...
}

Include Related Taxonomy fields in WordPress Search Results

The following example show you how to display posts when a search query matches the name, description or custom field of one of its chosen taxonomy terms.

Using the posts_join filter we SQL JOIN all the required taxonomy tables (term_relationships, term_taxonomy, terms, and termmeta).

The posts_groupby filter is used to group search results by post ID and post_type, this stops the results displaying duplicates.

Adding a filter onto ‘posts_where’ allows us to modify the search SQL and insert our custom logic, we first capture the search query by using preg_replace which uses a regular expression to find the default post_title LIKE value (The regex looks for the search value after post_title that is wrapped inside of single quotes ‘, by using [^\’] to capture all characters that do not match a single quote), we then use an SQL OR condition to append our taxonomy name search query, referencing the previously joined taxonomy table and checking to see if the name column matches the captured search keywords.

function jcas_setup($query)
{
    // escape if not the main wordpress search query
    if (is_admin() || !$query->is_search() || !$query->is_main_query()) {
        return;
    }

    // install our search customization hooks or code here.

    add_filter('posts_join', 'jcas_post_join');
    add_filter('posts_where', 'jcas_post_where');
    add_filter('posts_groupby', 'jcas_posts_groupby');
}

function jcas_post_join($join)
{
    global $wpdb;

    $join .= " LEFT JOIN {$wpdb->term_relationships} jct1_tr ON {$wpdb->posts}.ID = jct1_tr.object_id
        LEFT JOIN {$wpdb->term_taxonomy} jct1_tt ON jct1_tr.term_taxonomy_id = jct1_tt.term_id
        LEFT JOIN {$wpdb->terms} jct1_t ON jct1_tt.term_id = jct1_t.term_id
        LEFT JOIN {$wpdb->termmeta} jct1_tm ON jct1_t.term_id = jct1_tm.term_id ";

    // uninstall hook
    remove_filter('posts_join', 'jcas_post_join');

    return $join;
}

function jcas_posts_groupby($groupby)
{
    global $wpdb;

    $groupby .= "{$wpdb->posts}.ID, {$wpdb->posts}.post_type";

    remove_filter('posts_groupby', 'jcas_posts_groupby');
    return $groupby;
}

function jcas_post_where($where)
{
    $where = preg_replace(
        "/post_title LIKE (\'[^\']+\')\s*\)/",
        "post_title LIKE $1) 
            OR ( jct1_t.name LIKE $1 ) ",
        $where
    );

    // uninstall hook
    remove_filter('posts_where', 'jcas_post_where');

    return $where;
}

Search WordPress taxonomy name and description fields

To include the term description text in WordPress search query we need to use the description column in term_taxonomy table.

function jcas_post_where($where)
{
    $where = preg_replace(
        "/post_title LIKE (\'[^\']+\')\s*\)/",
        "post_title LIKE $1) 
            OR ( jct1_t.name LIKE $1 ) 
            OR ( jct1_tt.description LIKE $1 ) ",
        $where
    );

    // ...
}

Search fields belonging to a specific WordPress taxonomy

To search related taxonomy custom fields under a specific taxonomy we can use the taxonomy column in the term_taxonomy table.

function jcas_post_where($where)
{
    $where = preg_replace(
        "/post_title LIKE (\'[^\']+\')\s*\)/",
        "post_title LIKE $1)
            OR (
                jct1_tt.taxonomy = 'category'
                AND ( 
                    ( jct1_t.name LIKE $1 ) 
                    OR ( jct1_tt.description LIKE $1 )
                )
            ) ",
        $where
    );

    // ...
}

Search WordPress taxonomy custom fields

Including taxonomy custom fields in the wordpress search results can be achieved by including the meta_value column from the termmeta table.

function jcas_post_where($where)
{
    $where = preg_replace(
        "/post_title LIKE (\'[^\']+\')\s*\)/",
        "post_title LIKE $1) 
            OR ( jct1_t.name LIKE $1 ) 
            OR ( jct1_tt.description LIKE $1 ) 
            OR ( jct1_tm.meta_value LIKE $1 ) ",
        $where
    );

    // ...
}

Search WordPress specific taxonomy custom fields

Including a specific taxonomy custom field by referencing its name can be accomplished using the meta_key and meta_value columns of the termmeta table.

function jcas_post_where($where)
{
    $where = preg_replace(
        "/post_title LIKE (\'[^\']+\')\s*\)/",
        "post_title LIKE $1) 
            OR ( jct1_t.name LIKE $1 ) 
            OR ( jct1_tt.description LIKE $1 ) 
            OR (
                jct1_tm.meta_key = '_custom_field_1'
                AND jct1_tm.meta_value LIKE $1
            )
            OR (
                jct1_tm.meta_key = '_custom_field_2'
                AND jct1_tm.meta_value LIKE $1
            ) ",
        $where
    );

    // ...
}

Search WordPress taxonomy custom fields using wildcard characters in the name

Including a list of taxonomy custom fields by referencing its name including a % (wildcard character) can be accomplished using the meta_key and meta_value columns of the termmeta table.

function jcas_post_where($where)
{
    $where = preg_replace(
        "/post_title LIKE (\'[^\']+\')\s*\)/",
        "post_title LIKE $1) 
            OR ( jct1_t.name LIKE $1 ) 
            OR ( jct1_tt.description LIKE $1 ) 
            OR (
                jct1_tm.meta_key LIKE '_custom_field_%'
                AND jct1_tm.meta_value LIKE $1
            ) ",
        $where
    );

    // ...
}

Leave a Reply

Fields marked with an * are required to post a comment