51

I have created a custom post type and have attached some custom fields to it. Now I would like to the search that authors can perform on the custom post list screen (in the admin backend) to also be performed on the meta fields and not only look in the title and content as usual.

Where can I hook in and what code I have to use?

Example image enter image description here

Stefano

2

6 Answers 6

49

I solved filtering the query by adding the join on the postmeta table and changing the where clause. tips on filtering the WHERE clause (often require regular expression search&replace) are here on codex:

add_filter( 'posts_join', 'segnalazioni_search_join' );
function segnalazioni_search_join ( $join ) {
    global $pagenow, $wpdb;

    // I want the filter only when performing a search on edit page of Custom Post Type named "segnalazioni".
    if ( is_admin() && 'edit.php' === $pagenow && 'segnalazioni' === $_GET['post_type'] && ! empty( $_GET['s'] ) ) {    
        $join .= 'LEFT JOIN ' . $wpdb->postmeta . ' ON ' . $wpdb->posts . '.ID = ' . $wpdb->postmeta . '.post_id ';
    }
    return $join;
}

add_filter( 'posts_where', 'segnalazioni_search_where' );
function segnalazioni_search_where( $where ) {
    global $pagenow, $wpdb;

    // I want the filter only when performing a search on edit page of Custom Post Type named "segnalazioni".
    if ( is_admin() && 'edit.php' === $pagenow && 'segnalazioni' === $_GET['post_type'] && ! empty( $_GET['s'] ) ) {
        $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 );
        $where.= " GROUP BY {$wpdb->posts}.id"; // Solves duplicated results
    }
    return $where;
}
5
  • 1
    Wow! Just what I've been looking for. However, I think I may of found a bug, when searching on the post title, I get a match which is then duplicated in the results 5 times!?! imgur.com/eE52gIA
    – jnthnclrk
    Commented Aug 22, 2013 at 14:58
  • Here is another grab with the SQL printed: tinypic.com/view.php?pic=124tqb6&s=5 Can't figure out why I get 5 items!?!
    – jnthnclrk
    Commented Aug 22, 2013 at 20:19
  • 1
    Posted a separate question on fixing the dupe bug: wordpress.stackexchange.com/questions/111185/…
    – jnthnclrk
    Commented Aug 24, 2013 at 11:47
  • This and the post below were useful to me. Now to find a way to include searching for the post author and show posts done by them. Commented Jul 31, 2018 at 18:27
  • @Stefano, search result is working. Have an issue, default field "Post title", search record is repeating many time and admin side. See:imgur.com/a/W4wmXhO Commented Oct 23, 2018 at 9:16
14

Stefano answer is great but it lacks a distinct clause :

function segnalazioni_search_distinct( $where ){
    global $pagenow, $wpdb;

    if ( is_admin() && $pagenow=='edit.php' && $_GET['post_type']=='segnalazioni' && $_GET['s'] != '') {
    return "DISTINCT";

    }
    return $where;
}
add_filter( 'posts_distinct', 'segnalazioni_search_distinct' );

Add the code above update it and it will work with no duplicates.

9

This will work,

function custom_search_query( $query ) {
    $custom_fields = array(
        // put all the meta fields you want to search for here
        "rg_first_name",
        "rg_1job_designation"
    );
    $searchterm = $query->query_vars['s'];

    // we have to remove the "s" parameter from the query, because it will prevent the posts from being found
    $query->query_vars['s'] = "";

    if ($searchterm != "") {
        $meta_query = array('relation' => 'OR');
        foreach($custom_fields as $cf) {
            array_push($meta_query, array(
                'key' => $cf,
                'value' => $searchterm,
                'compare' => 'LIKE'
            ));
        }
        $query->set("meta_query", $meta_query);
    };
}
add_filter( "pre_get_posts", "custom_search_query");
6
  • 1
    Pleas properly indent your code, and include an explanation as to why and how this will work.
    – tfrommen
    Commented Jan 31, 2014 at 11:28
  • Although I first upvoted it, I realised that this will, unfortunately, work on every search this same it might break front end search. Commented Jun 13, 2018 at 13:01
  • 1
    Adding a check for if ( $query->query['post_type'] != 'your_custom_post_type' ){ return; } top the top of the function will stop this from running on other searches. Note that the technique in this answer no longer search the post_title and adding that back in is not trivial.
    – jwinn
    Commented Nov 30, 2018 at 18:21
  • One further problem - The indicator Search results for “<keyword>” calls get_search_query() which further calls get_query_var( 's' ). Since "s" is set to empty string, Search results for “” will always have an empty value between the quotes. Is there a tweak to this solution that gets around this?
    – jschrab
    Commented Jan 29, 2019 at 21:01
  • 1
    Another problem, this solution will only work for meta values now. Searchs by title for example, won't work. Commented Jun 16, 2022 at 16:50
5

Solution 1: Add this code in function file, and change and add more columns name, that you have used in your custom post type

function extend_admin_search( $query ) {

    // use your post type
    $post_type = 'document';
    // Use your Custom fields/column name to search for
    $custom_fields = array(
        "_file_name",
    );

    if( ! is_admin() )
        return;
    
    if ( $query->query['post_type'] != $post_type )
        return;

    $search_term = $query->query_vars['s'];

    // Set to empty, otherwise it won't find anything
    $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 );
    };
}

add_action( 'pre_get_posts', 'extend_admin_search' );

Solution 2: (Recommended) Use this code in function file without any change

function cf_search_join( $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', 'cf_search_join' );
function cf_search_where( $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', 'cf_search_where' );

function cf_search_distinct( $where ) {
    global $wpdb;

    if ( is_search() ) {
        return "DISTINCT";
    }

    return $where;
}
add_filter( 'posts_distinct', 'cf_search_distinct' );
4

With this code you can search in post list in WordPress Admin Panel with custom post meta values along with title and other default fields.

Please, add below code in functions.php file:

if (!function_exists('extend_admin_search')) {
    add_action('admin_init', 'extend_admin_search');

    /**
     * hook the posts search if we're on the admin page for our type
     */
    function extend_admin_search() {
        global $typenow;

        if ($typenow === 'your_custom_post_type') {
            add_filter('posts_search', 'posts_search_custom_post_type', 10, 2);
        }
    }

    /**
     * add query condition for custom meta
     * @param string $search the search string so far
     * @param WP_Query $query
     * @return string
     */
    function posts_search_custom_post_type($search, $query) {
        global $wpdb;

        if ($query->is_main_query() && !empty($query->query['s'])) {
            $sql    = "
            or exists (
                select * from {$wpdb->postmeta} where post_id={$wpdb->posts}.ID
                and meta_key in ('custom_field1','custom_field2')
                and meta_value like %s
            )
        ";
            $like   = '%' . $wpdb->esc_like($query->query['s']) . '%';
            $search = preg_replace("#\({$wpdb->posts}.post_title LIKE [^)]+\)\K#",
                $wpdb->prepare($sql, $like), $search);
        }

        return $search;
    }
}
2
  • Please don't copy your answer to each matching question. Once you have sufficient reputation you will be able to comment on any post and provide a link to your answer.
    – nmr
    Commented Jan 21, 2020 at 11:13
  • I tried to help everyone with similar problems and I worked hard to find the right answer. Commented Jan 22, 2020 at 11:24
0

The version of the code here in a couple answers that modifies the meta_query parameter of the search's WP_Query in pre_get_posts was no longer searching the post_title. Adding the ability to search either the post title, OR meta values can't be done directly in WP_Query without modifying SQL unfortunately, as this question elaborates upon: Using meta query ('meta_query') with a search query ('s')

I've combined some of the techniques here to get a working version that avoids preg_replaces and too much SQL modification (I wish it could be avoided entirely). The only downside being that after a search, subtitle text at the top of the page says "Search results for ''". I've just hidden that with CSS for my plugin's custom post type.

/**
 * Extend custom post type search to also search meta fields
 * @param  WP_Query $query
 */
function extend_cpt_admin_search( $query ) {
  // Make sure we're in the admin area and that this is our custom post type
  if ( !is_admin() || $query->query['post_type'] != 'your_custom_post_type' ){
    return;
  }

  // Put all the meta fields you want to search for here
  $custom_fields = array(
    "your_custom_meta_field",
    "your_custom_meta_field2",
    "your_custom_meta_field3"
  );
  // The string submitted via the search form
  $searchterm = $query->query_vars['s'];

  // Set to empty, otherwise no results will be returned.
  // The one downside is that the displayed search text is empty at the top of the page.
  $query->query_vars['s'] = '';

  if ($searchterm != ""){
    // Add additional meta_query parameter to the WP_Query object.
    // Reference: https://codex.wordpress.org/Class_Reference/WP_Query#Custom_Field_Parameters
    $meta_query = array();
    foreach($custom_fields as $cf) {
      array_push($meta_query, array(
        'key' => $cf,
        'value' => $searchterm,
        'compare' => 'LIKE'
      ));
    }
    // Use an 'OR' comparison for each additional custom meta field.
    if (count($meta_query) > 1){
      $meta_query['relation'] = 'OR';
    }
    // Set the meta_query parameter
    $query->set('meta_query', $meta_query);


    // To allow the search to also return "OR" results on the post_title
    $query->set('_meta_or_title', $searchterm);
  }
}
add_action('pre_get_posts', 'extend_cpt_admin_search');



/**
 * WP_Query parameter _meta_or_title to allow searching post_title when also
 * checking searching custom meta values
 * https://wordpress.stackexchange.com/questions/78649/using-meta-query-meta-query-with-a-search-query-s
 * https://wordpress.stackexchange.com/a/178492
 * This looks a little scary, but basically it's modifying the WHERE clause in the 
 * SQL to say "[like the post_title] OR [the existing WHERE clause]"
 * @param  WP_Query $q
 */
function meta_or_title_search( $q ){
  if( $title = $q->get( '_meta_or_title' ) ){
    add_filter( 'get_meta_sql', function( $sql ) use ( $title ){
      global $wpdb;

      // Only run once:
      static $nr = 0;
      if( 0 != $nr++ ) return $sql;

      // Modified WHERE
      $sql['where'] = sprintf(
          " AND ( (%s) OR (%s) ) ",
          $wpdb->prepare( "{$wpdb->posts}.post_title LIKE '%%%s%%'", $title),
          mb_substr( $sql['where'], 5, mb_strlen( $sql['where'] ) )
      );

      return $sql;
    });
  }
}
add_action('pre_get_posts', 'meta_or_title_search');

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.