Complex WooCommerce product search rarely depends on one condition. A shopper may need products that match a phrase, belong to a specific category, fall within a price range, include selected attributes, and exclude discontinued or hidden items. Elasticsearch combines these requirements through the bool query.
How the bool query is structured
A bool query combines other queries through four primary clauses:
must: the document must match. Scores contribute to relevance.filter: the document must match, but the clause does not affect relevance scoring. Filters are generally the best choice for exact values, ranges, and visibility rules.should: an optional relevance or preference clause, unless the query has nomustorfilterclauses.must_not: the document must not match.
For WooCommerce integrations, put business constraints in filter whenever possible and reserve must or should for text relevance. This produces more predictable scoring and can improve cacheability for repeated filters.
A practical product search requirement
Assume the search endpoint must return products that:
- match “waterproof hiking boots” in the product name or description;
- are published and visible in the catalog;
- cost between 100 and 250;
- belong to either the hiking or footwear category;
- have a waterproof attribute;
- are not marked as discontinued.
A corresponding query could look like this:
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "waterproof hiking boots",
"fields": ["name^3", "short_description", "description"]
}
}
],
"filter": [
{ "term": { "status": "publish" } },
{ "term": { "catalog_visibility": "visible" } },
{ "range": { "price": { "gte": 100, "lte": 250 } } },
{
"terms": {
"category_ids": ["hiking", "footwear"]
}
},
{ "term": { "attributes.waterproof": "yes" } }
],
"must_not": [
{ "term": { "discontinued": true } }
]
}
}
}
The multi_match clause contributes relevance, while the exact status, visibility, price, category, attribute, and exclusion conditions act as constraints. The category filter above means that at least one of the listed category values must be present. If the requirement is that a product belongs to both category groups, use separate term filters instead.
Using should for alternatives and relevance boosts
The should clause is useful when several alternatives are acceptable or when some matches should rank higher without being mandatory. For example, an agency might want to search the product name and description while boosting exact matches in the name and products with a preferred brand:
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "trail jacket",
"fields": ["name", "description"]
}
}
],
"should": [
{ "match_phrase": { "name": { "query": "trail jacket", "boost": 5 } } },
{ "term": { "brand.keyword": { "value": "northpeak", "boost": 2 } } },
{ "term": { "featured": { "value": true, "boost": 1.2 } } }
],
"filter": [
{ "term": { "status": "publish" } }
]
}
}
}
Because this query contains a must clause, the should clauses are optional by default. A product does not need to be from the preferred brand or featured, but matching either condition can improve its position in the results.
Requiring one or more alternatives
When should represents alternatives that must be satisfied, set minimum_should_match. This is common for faceted search requirements such as “red or blue” or “cotton or linen.”
{
"query": {
"bool": {
"filter": [
{
"bool": {
"should": [
{ "term": { "color.keyword": "red" } },
{ "term": { "color.keyword": "blue" } }
],
"minimum_should_match": 1
}
}
]
}
}
}
Do not rely on the default behavior when building dynamic queries. Elasticsearch changes the effective requirement for should depending on whether the surrounding bool query has must or filter clauses. Setting minimum_should_match explicitly makes generated queries easier to reason about.
Combining AND and OR logic
WooCommerce filters often need grouped logic. Consider the requirement: products must be in stock and published, and must satisfy either “red and large” or “blue and medium.” The groups can be represented as nested bool queries:
{
"query": {
"bool": {
"filter": [
{ "term": { "status": "publish" } },
{ "term": { "stock_status": "instock" } },
{
"bool": {
"should": [
{
"bool": {
"filter": [
{ "term": { "color.keyword": "red" } },
{ "term": { "size.keyword": "large" } }
]
}
},
{
"bool": {
"filter": [
{ "term": { "color.keyword": "blue" } },
{ "term": { "size.keyword": "medium" } }
]
}
}
],
"minimum_should_match": 1
}
}
]
}
}
}
Each inner bool represents an AND group, and the outer inner bool combines those groups with OR logic. This pattern is safer than placing all color and size values in independent terms filters, which could incorrectly match a red medium or blue large product.
Handling WooCommerce attribute data
The correct query depends on the Elasticsearch mapping created by the integration. Exact attribute filters generally require a keyword field or another non-analyzed field. A text field analyzed with a standard analyzer is not a reliable target for exact values such as red, XL, or cotton.
Common mappings include flat keyword fields such as attributes.color.keyword, arrays of keyword values such as attribute_terms, or nested attribute objects. If attributes are indexed as nested objects, use a nested query so values from different attribute objects cannot be incorrectly combined:
{
"query": {
"nested": {
"path": "attributes",
"query": {
"bool": {
"filter": [
{ "term": { "attributes.name": "color" } },
{ "term": { "attributes.value": "red" } }
]
}
}
}
}
}
Before implementing a filter, inspect the mapping and a representative indexed product with the Elasticsearch mapping and document APIs. A query written for a flat field will not behave the same way against a nested field.
Excluding products with must_not
Use must_not for rules such as excluding private products, hidden catalog items, restricted brands, or products with a discontinued flag:
{
"query": {
"bool": {
"filter": [
{ "term": { "status": "publish" } }
],
"must_not": [
{ "terms": { "brand.keyword": ["clearance-only", "restricted"] } },
{ "term": { "visibility.keyword": "hidden" } }
]
}
}
}
In a must_not clause, matching any listed query excludes the document. Keep sensitive catalog rules server-side rather than accepting arbitrary exclusion fields from the browser.
Building bool queries in a WooCommerce service
When an agency generates queries from request parameters, build each clause from an allowlist of supported filters. Validate numeric ranges, normalize attribute values, remove empty arrays, and use parameterized query objects rather than concatenating JSON strings. A typical builder can maintain separate collections for required text queries, filter queries, optional boosts, and exclusions.
const filter = [
{ term: { status: 'publish' } },
{ range: { price: { gte: minPrice, lte: maxPrice } } }
];
if (categoryIds.length) {
filter.push({ terms: { category_ids: categoryIds } });
}
const query = {
bool: {
must: searchText ? [{ multi_match: {
query: searchText,
fields: ['name^3', 'description']
} }] : [],
filter,
must_not: [{ term: { discontinued: true } }]
}
};
Use the filter context for constraints that should not influence ranking. If no search text is supplied, a bool query containing only filters is still valid and behaves like a filtered catalog query.
Testing and troubleshooting
- Use the
_validate/queryAPI to catch malformed generated queries. - Use the
_explainAPI on a specific product to understand why it matched and how it scored. - Test combinations that expose grouping errors, such as multiple colors, multiple sizes, and mutually exclusive categories.
- Confirm whether WooCommerce prices are indexed as numeric values and whether currency conversion occurs before indexing or at query time.
- Check that product, variation, stock, and visibility updates are synchronized before diagnosing a query as incorrect.
- Profile slow searches with the Elasticsearch Profile API, then review expensive text clauses, scripts, nested queries, and large disjunctions.
Boolean queries provide a composable foundation for WooCommerce search: text clauses control relevance, filters enforce catalog rules, should clauses express alternatives or ranking preferences, and must_not clauses protect against unwanted results. Clear grouping and a mapping-aware query builder are essential when these rules are generated from layered storefront filters.