Lately, my client requires me to add the ability to search by custom fields at the backend. Today I gonna share what I did.
There are three filters we will use.
Below is what page will be implemented.

You can add the code below to functions.php or your own plugin. $_GET[‘s’] is the search input on the edit page. The posts_distinct filter is needed to prevent duplicate data.
add_filter('posts_join', 'package_customfields_search_join');
function package_customfields_search_join($join)
{
global $pagenow, $wpdb;
if (is_admin() && 'edit.php' === $pagenow && 'packages' === $_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', 'package_customfields_search_where');
function package_customfields_search_where($where)
{
global $pagenow, $wpdb;
if (is_admin() && 'edit.php' === $pagenow && 'packages' === $_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
);
}
return $where;
}
add_filter('posts_distinct', 'package_search_distinct');
function package_search_distinct($where)
{
global $pagenow, $wpdb;
if (is_admin() && 'edit.php' === $pagenow && 'packages' === $_GET['post_type'] && !empty($_GET['s']) && is_search()) {
return "DISTINCT";
}
return $where;
}
Yeap! that’s it. If you are looking for adding the custom fields filter, you may check my post here.