Table of contents :

Combining Search and Filters in One Query

100-days-of-elasticsearch-woocommerce-018

Table of contents :

WooCommerce shoppers often need two things at the same time: relevant text results and precise product constraints. A customer may search for waterproof hiking boots while also selecting a brand, price range, stock status, and product category. Elasticsearch can handle these requirements in one request by combining a scored search clause with non-scoring filters inside a Boolean query.

Use must for search and filter for constraints

The usual structure is an Elasticsearch bool query. Put the full-text portion in must or should, and put exact restrictions in filter. Filter clauses narrow the result set without changing relevance scores, and Elasticsearch can optimize them efficiently.

{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "waterproof hiking boots",
            "fields": [
              "name^4",
              "short_description^2",
              "description",
              "sku"
            ],
            "type": "best_fields",
            "operator": "and"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "stock_status.keyword": "instock"
          }
        },
        {
          "terms": {
            "brand.keyword": ["Alpine", "Summit"]
          }
        },
        {
          "range": {
            "price": {
              "gte": 100,
              "lte": 250
            }
          }
        }
      ]
    }
  },
  "sort": [
    "_score",
    {"price": "asc"}
  ],
  "from": 0,
  "size": 24
}

The field names in this example depend on the index mapping used by the agency or search integration. A WooCommerce implementation may store the product title as name, the regular or active price as price, and taxonomy or attribute values in fields such as categories.slug, pa_brand.keyword, or attributes.color.keyword. Confirm the mapping before generating queries dynamically.

Why filters should not be placed in the text query

Passing every condition through must can produce technically correct results, but it makes the query less clear and can affect scoring unnecessarily. A price range is not evidence that one product is more relevant than another. The same is true for stock status, category membership, brand, color, and most WooCommerce attributes.

Use filter for exact or numeric conditions:

  • term for one exact keyword value, such as instock.
  • terms for any value from a selected set, such as several brands.
  • range for prices, ratings, dates, dimensions, or other numeric values.
  • exists when a product must contain a field.

Use analyzed fields for human language search and keyword or numeric fields for filters. A request against name.keyword is generally not a substitute for searching an analyzed product-name field, while a term query against an analyzed field may not behave as expected for exact taxonomy values.

Building a WooCommerce query from request parameters

An agency integration commonly receives a search phrase and filter parameters from a storefront request. Build the Elasticsearch body from validated values rather than concatenating raw JSON or user input.

const must = [];
const filter = [];

if (search.trim()) {
  must.push({
    multi_match: {
      query: search.trim(),
      fields: ['name^4', 'short_description^2', 'description', 'sku'],
      type: 'best_fields'
    }
  });
}

if (categorySlugs.length) {
  filter.push({
    terms: { 'categories.slug.keyword': categorySlugs }
  });
}

if (brandSlugs.length) {
  filter.push({
    terms: { 'pa_brand.slug.keyword': brandSlugs }
  });
}

if (minPrice !== undefined || maxPrice !== undefined) {
  const priceRange = {};
  if (minPrice !== undefined) priceRange.gte = minPrice;
  if (maxPrice !== undefined) priceRange.lte = maxPrice;
  filter.push({ range: { price: priceRange } });
}

filter.push({ term: { 'stock_status.keyword': 'instock' } });

const body = {
  query: {
    bool: {
      ...(must.length ? { must } : { must: [{ match_all: {} }] }),
      filter
    }
  },
  from: page * pageSize,
  size: pageSize
};

Validate numeric values before adding a range clause, restrict arrays to recognized taxonomy or attribute values where appropriate, and cap the requested page size. This prevents malformed queries and protects the Elasticsearch cluster from expensive or abusive requests.

Handling optional search terms

When a search phrase is optional, the query should still return products that satisfy the filters. The simplest approach is to omit the text clause when the search value is empty and use match_all with the filters. If a query contains filter or must clauses, should clauses are normally optional unless minimum_should_match is set explicitly.

{
  "query": {
    "bool": {
      "should": [
        {
          "multi_match": {
            "query": "trail shoes",
            "fields": ["name^4", "description"]
          }
        },
        {
          "match": {
            "sku": "trail shoes"
          }
        }
      ],
      "minimum_should_match": 1,
      "filter": [
        {"term": {"stock_status.keyword": "instock"}},
        {"range": {"price": {"lte": 200}}}
      ]
    }
  }
}

Set minimum_should_match when at least one optional text clause must match. Without it, a Boolean query that already has a required clause can treat should clauses as scoring preferences rather than requirements.

Combining filters with product aggregations

Faceted navigation usually needs aggregations for brands, categories, sizes, and colors. If the aggregations should describe only the currently filtered result set, place the constraints in the main query’s filter array. The aggregation will then operate on the filtered documents.

{
  "query": {
    "bool": {
      "must": [
        {"multi_match": {
          "query": "running jacket",
          "fields": ["name^4", "description"]
        }}
      ],
      "filter": [
        {"term": {"stock_status.keyword": "instock"}},
        {"range": {"price": {"gte": 50, "lte": 180}}}
      ]
    }
  },
  "aggs": {
    "brands": {
      "terms": {
        "field": "brand.keyword",
        "size": 30
      }
    }
  }
}

Sometimes the user interface should show facet counts from the broader result set while excluding only the current selection for a particular facet. In that case, consider a post_filter. It filters returned hits after aggregations are calculated, so it is useful for selected-facet behavior but should not be used accidentally. A post_filter does not replace the main query filter when aggregations must reflect all active constraints.

Common implementation issues

  • Filtering an analyzed field: use a keyword subfield or a dedicated normalized field for exact values.
  • Using text search for prices: map prices as numeric values and use range.
  • Ignoring variations: decide whether the index represents parent products, variations, or both, and ensure availability and price filters use the correct document fields.
  • Applying stock logic inconsistently: define whether a variable product is available when any variation is in stock or only when a selected variation is available.
  • Allowing arbitrary sort fields: map storefront sort options to an allowlist instead of accepting a field name directly from the request.
  • Paginating deeply: use search_after for large result sets rather than relying on very high from values.

Testing the combined query

Test search and filters independently, then test their combinations. Include empty searches, multiple selected values, minimum-only and maximum-only price ranges, products with missing attributes, out-of-stock variations, and sorting with equal scores. Inspect the generated request with representative WooCommerce data and use Elasticsearch’s profile tools only during controlled diagnostics, since profiling adds overhead.

A well-structured Boolean query keeps relevance scoring focused on the customer’s language while filters enforce the commercial rules of the catalog. That separation makes the integration easier to extend when an agency adds new attributes, merchandising rules, facets, or storefront sorting options.

Trending posts
You might also like