Filter - Search: How to catch not exact match results?

When I use the Filter - Search element I am able to do beautiful things with it. However I have one big problem.

If I have a result titled “Big Red Button“ and and search for “Big Button” it doesn’t gives the “Big Red Button“ as a result. I can’t expect the visitors to know the product’s exact name when they search.

Here is an exact match:

Not exact match (no results):

How can I solve this issue? Here is my configuration screen:

Thank you!

1 Like

Bricks currently treats the Filter - Search input as the search phrase. It does not split “Big Button” into separate keywords or run fuzzy matching against “Big Red Button” out of the box.

There is already a related feature request topic for fuzzy/less-exact matching (though it hasn’t gained much traction so far) you already replied to :slight_smile:

Thank you. Is there any way we can implement this functionality? Is there any hook I can poke somehow to make this work? A webshop or even any site with proper search functionality is bad for the site owners and the visitors too because if they don’t find the desired result they will leave.

Not exactly… Of course, you could save the alternate phrases as custom field(s) and include them in the search criteria meta keys.

Yes but having thousands of pages/posts/products it is not viable to enter search terms into every item.

However I solved the issue with his code. It works well on my side, check this:

Can you approve it, do you see any mistake?


/**
 * Smart Search: Splits the search query into words and searches for all of them (AND relationship).
 * E.g., searching for "nagy táska" will find "nagy fehér táska".
 */
add_filter( 'posts_search', function( $search, $wp_query ) {
    global $wpdb;

    if ( empty( $search ) ) {
        return $search;
    }

    // Run only on frontend and AJAX/REST requests (do not interfere in admin area, e.g., product lists)
    if ( is_admin() && ! ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
        return $search;
    }

    $search_terms = isset( $wp_query->query_vars['s'] ) ? $wp_query->query_vars['s'] : '';
    if ( empty( $search_terms ) ) {
        return $search;
    }

    // Split search terms by spaces
    $terms = array_filter( array_map( 'trim', explode( ' ', $search_terms ) ) );
    if ( count( $terms ) <= 1 ) {
        return $search;
    }

    $search = '';
    foreach ( $terms as $term ) {
        $term = esc_sql( $wpdb->esc_like( $term ) );
        // Each word must appear in the title, content, or excerpt (AND logic)
        $search .= " AND (({$wpdb->posts}.post_title LIKE '%{$term}%') OR ({$wpdb->posts}.post_content LIKE '%{$term}%') OR ({$wpdb->posts}.post_excerpt LIKE '%{$term}%'))";
    }

    return $search;
}, 500, 2 );

add_filter( 'bricks/query_filters/filter_query_vars', function( $query_vars, $filter, $query_id, $index ) {
    $instance_name = isset( $filter['instance_name'] ) ? $filter['instance_name'] : '';
    
    if ( $instance_name === 'filter-search' ) {
        $search_value = isset( $filter['value'] ) ? $filter['value'] : '';
        if ( ! empty( $search_value ) ) {
            // If Bricks found no results and cleared the query, remove this restriction
            if ( isset( $query_vars['post__in'] ) && $query_vars['post__in'] === array( 0 ) ) {
                unset( $query_vars['post__in'] );
            }
            // Start standard WordPress search
            $query_vars['s'] = $search_value;
            $query_vars['orderby'] = 'relevance';
        }
    }
    return $query_vars;
}, 20, 4 );

Nice approach, and the fallback idea makes sense. However, I would tighten the snippet a bit before using it in production.

The main thing I’d avoid is replacing posts_search globally for every frontend/AJAX search. It is safer to mark only this specific fallback query and then only adjust the SQL for that query. I’d also use $wpdb->prepare(), split by any whitespace, and keep WordPress’ password-protected post condition for logged-out visitors.

Something like this should be safer (untested):

add_filter( 'bricks/query_filters/filter_query_vars', function( $query_vars, $filter, $query_id, $index ) {
	if ( ( $filter['instance_name'] ?? '' ) !== 'filter-search' ) {
		return $query_vars;
	}

	$search_value = trim( (string) ( $filter['value'] ?? '' ) );

	if ( $search_value === '' ) {
		return $query_vars;
	}

	// If Bricks custom search returned no IDs, fall back to native WP search.
	if ( isset( $query_vars['post__in'] ) && $query_vars['post__in'] === [ 0 ] ) {
		unset( $query_vars['post__in'] );

		$query_vars['s']                = $search_value;
		$query_vars['orderby']          = 'relevance';
		$query_vars['brx_smart_search'] = true;
	}

	return $query_vars;
}, 20, 4 );

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

	if ( ! $wp_query->get( 'brx_smart_search' ) ) {
		return $search;
	}

	$search_terms = trim( (string) $wp_query->get( 's' ) );

	if ( $search_terms === '' ) {
		return $search;
	}

	$terms = preg_split( '/\s+/', $search_terms, -1, PREG_SPLIT_NO_EMPTY );

	if ( count( $terms ) < 2 ) {
		return $search;
	}

	$parts = [];

	foreach ( $terms as $term ) {
		$like    = '%' . $wpdb->esc_like( $term ) . '%';
		$parts[] = $wpdb->prepare(
			"({$wpdb->posts}.post_title LIKE %s OR {$wpdb->posts}.post_content LIKE %s OR {$wpdb->posts}.post_excerpt LIKE %s)",
			$like,
			$like,
			$like
		);
	}

	$search = ' AND ' . implode( ' AND ', $parts );

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

	return $search;
}, 500, 2 );

Thank you! I am testing it and it seems to be working as well, hopefully better than my solution :slight_smile:

1 Like