Many websites built by WordPress that use ACF put the vast majority of content in Custom Fields (and not the main editor) and for good reason, it provides a much nicer interface to work with and reduce error.

Unfortunately a byproduct of this means that hardly any of your site content is considered when performing searches. That’s a huge problem!

In order to fix that we need to modify the WordPress search query to include custom fields.

Add this code to your functions.php file to enable searching WordPress by custom fields. This will also allow you to search the default wordpress search form.

You can also exclude some fields from search - just put meta key this array -- $exclude_acf_fields



//custom code for add acf in search
function include_all_acf_fields( $search, $wp_query ) {
    global $wpdb;

    if ( empty( $search ) ) {
        return $search; // skip processing - no search term in query
    }

    $q = $wp_query->query_vars;
    $n = ! empty( $q['exact'] ) ? '' : '%';
    $search = '';
    $searchand = '';

    // Array for excluding specific ACF fields
    $exclude_acf_fields = array('services_%_service_title','sub_title'); // Replace with actual field names to exclude

    // Fetch all meta keys for the posts being searched
    $acf_fields = $wpdb->get_col("
        SELECT DISTINCT meta_key
        FROM $wpdb->postmeta
        WHERE meta_key NOT LIKE '\_%'
    ");

    // Function to check if a meta key should be excluded
    function should_exclude($meta_key, $exclude_patterns) {
        foreach ($exclude_patterns as $pattern) {
            if (strpos($pattern, '%') !== false) {
                // Convert wildcard pattern to a regex pattern
                $regex = '/^' . str_replace('%', '\d+', preg_quote($pattern, '/')) . '$/';
                if (preg_match($regex, $meta_key)) {
                    return true;
                }
            } elseif ($meta_key === $pattern) {
                return true;
            }
        }
        return false;
    }

    // Exclude specified fields
    $acf_fields = array_filter($acf_fields, function($field) use ($exclude_acf_fields) {
        return !should_exclude($field, $exclude_acf_fields);
    });

    foreach ( (array) $q['search_terms'] as $term ) {
        $term = esc_sql( $wpdb->esc_like( $term ) );
        $search .= "{$searchand}(
            $wpdb->posts.post_title LIKE '{$n}{$term}{$n}'
            OR $wpdb->posts.post_content LIKE '{$n}{$term}{$n}'
        ";

        // Add ACF fields and subfields to the search
        foreach ($acf_fields as $acf_field) {
            $search .= " OR (CAST(acfmeta.meta_value AS CHAR) LIKE '{$n}{$term}{$n}' AND acfmeta.meta_key = '{$acf_field}')";
        }

        $search .= ")";
        $searchand = ' AND ';
    }

    if ( ! empty( $search ) ) {
        $search = " AND ({$search}) ";
        if ( ! is_user_logged_in() )
            $search .= " AND ($wpdb->posts.post_password = '') ";
    }

    return $search;
}

function left_join_acf_fields( $join, $wp_query ) {
    global $wpdb;

    if (!empty($wp_query->query_vars['s'])) {
        $join .= " LEFT JOIN $wpdb->postmeta acfmeta ON ($wpdb->posts.ID = acfmeta.post_id) ";
    }

    return $join;
}

function remove_duplicate_posts_distinct($distinct) {
    return "DISTINCT";
}

function all_posts_groupby($groupby) {
    global $wpdb;

    $groupby = "$wpdb->posts.ID";

    return $groupby;
}

add_filter( 'posts_search', 'include_all_acf_fields', 500, 2 );
add_filter( 'posts_join', 'left_join_acf_fields', 500, 2 );
add_filter( 'posts_distinct', 'remove_duplicate_posts_distinct' );
add_filter( 'posts_groupby', 'all_posts_groupby' );