Wordpress - How to make search include data from wp_postmeta?

Add this to your plugin or functions.php file of your theme. The example below will include 'your_key' in search. You can include all your keys by repeating the array.

function me_search_query( $query ) {
  if ( $query->is_search ) {
    $meta_query_args = array(
      array(
        'key' => 'your_key',
        'value' => $query->query_vars['s'] = '',
        'compare' => 'LIKE',
      ),
    );
    $query->set('meta_query', $meta_query_args);
  };
}
add_filter( 'pre_get_posts', 'me_search_query');

This function should work, even after WP 4.8.3 security update.

Usage:

// Do this in functions.php

add_meta_field_to_search_query('right_data');
add_meta_field_to_search_query('extra_search_terms');

Implementation:

/* ADD META FIELD TO SEARCH QUERY */

function add_meta_field_to_search_query($field){
  if(isset($GLOBALS['added_meta_field_to_search_query'])){
    $GLOBALS['added_meta_field_to_search_query'][] = '\'' . $field . '\'';

    return;
  }

  $GLOBALS['added_meta_field_to_search_query'] = array();
  $GLOBALS['added_meta_field_to_search_query'][] = '\'' . $field . '\'';

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

      if (is_search()){    
          $join .= " LEFT JOIN $wpdb->postmeta ON $wpdb->posts.ID = $wpdb->postmeta.post_id ";
      }

      return $join;
  });

  add_filter('posts_groupby', function($groupby){
      global $wpdb;

      if (is_search()) {    
          $groupby = "$wpdb->posts.ID";
      }

      return $groupby;
  });

  add_filter('posts_search', function($search_sql){
      global $wpdb;

      $search_terms = get_query_var('search_terms');

      if(!empty($search_terms)){
        foreach ($search_terms as $search_term){
            $old_or = "OR ({$wpdb->posts}.post_content LIKE '{$wpdb->placeholder_escape()}{$search_term}{$wpdb->placeholder_escape()}')";
            $new_or = $old_or . " OR ({$wpdb->postmeta}.meta_value LIKE '{$wpdb->placeholder_escape()}{$search_term}{$wpdb->placeholder_escape()}' AND {$wpdb->postmeta}.meta_key IN (" . implode(', ', $GLOBALS['added_meta_field_to_search_query']) . "))";
            $search_sql = str_replace($old_or, $new_or, $search_sql);
        }
      }

      $search_sql = str_replace( " ORDER BY ", " GROUP BY $wpdb->posts.ID ORDER BY ", $search_sql );

      return $search_sql;
  });
}

Several people have done this in different ways:

http://websmartdesign.nz/searching-structured-post-data-with-wordpress/ https://adambalee.com/search-wordpress-by-custom-fields-without-a-plugin/


This is an improved Ahmed's answer:

function me_search_query( $query ) {
  if ( $query->is_search ) {
    $meta_query_args = array(
      array(
        'key' => 'your_key',
        'value' => $query->query_vars['s'],
        'compare' => 'LIKE',
      ),
    );
    $query->set('meta_query', $meta_query_args);
    add_filter( 'get_meta_sql', 'me_replace_and_with_or' );
  };
}

function me_replace_and_with_or( $sql ) {
    if ( 1 === strpos( $sql['where'], 'AND' ) ) {
        $sql['where'] = substr( $sql['where'], 4 );
        $sql['where'] = ' OR ' . $sql['where'];
    }

    //make sure that this filter will fire only once for the meta query
    remove_filter( 'get_meta_sql', 'me_replace_and_with_or' );
    return $sql;
}

add_filter( 'pre_get_posts', 'me_search_query');

The problem is that WordPress generates meta query with "AND" operator, and it would show only posts which have search string in both places - title AND meta, or content AND meta. So, we need to create an additional filter to change "AND" to "OR" (and then remove that filter to not break anything else).