Elasticsearch Filter and Bool Queries

Real search features rarely run a single query. Most require combining conditions — "find laptops under ₹50,000 that are in stock and rated above 4 stars." The bool query is how you do that in Elasticsearch.

The Bool Query — Four Clauses

+-----------+--------------------------------------------------+
| Clause    | Meaning                                          |
+-----------+--------------------------------------------------+
| must      | Document MUST match — affects relevance score    |
| filter    | Document MUST match — does NOT affect score      |
| should    | Document SHOULD match — boosts score if it does  |
| must_not  | Document MUST NOT match                          |
+-----------+--------------------------------------------------+

Combine any number of clauses to build complex logic with full control over scoring.

must vs filter — The Key Difference

User searches for: "wireless headphones under ₹3000"

must clause (matches + scores):
  "wireless headphones" in name → score matters
  Elasticsearch ranks by how relevant the text match is

filter clause (matches but no score):
  price < 3000 → either yes or no
  There is no "how much under ₹3000" — it does not affect score
  Elasticsearch CACHES this result for speed

Put text searches in must and put numeric ranges, dates, and exact values in filter. This gives you both relevance ranking and fast cached filtering.

Basic Bool Query Example

GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "wireless headphones" } }
      ],
      "filter": [
        { "term":  { "category": "electronics" } },
        { "range": { "price": { "lte": 3000 } } },
        { "term":  { "in_stock": true } }
      ]
    }
  }
}

This finds electronics that are in stock, cost ₹3000 or less, and match the text "wireless headphones." Relevance score comes only from the text match.

Adding should — Bonus Matching

Documents matching a should clause score higher but are not excluded if they miss it.

GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "headphones" } }
      ],
      "should": [
        { "term": { "brand": "Sony" } },
        { "range": { "rating": { "gte": 4.5 } } }
      ],
      "filter": [
        { "term": { "in_stock": true } }
      ]
    }
  }
}

Every in-stock headphone appears in results. Sony headphones and highly rated ones appear near the top because the should clauses add to their score.

must_not — Excluding Results

GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "laptop" } }
      ],
      "must_not": [
        { "term": { "brand": "UnknownBrand" } },
        { "range": { "price": { "gt": 100000 } } }
      ]
    }
  }
}

Laptops from UnknownBrand and any laptop over ₹1 lakh are excluded entirely.

minimum_should_match

Control how many should clauses must match for a document to appear in results:

GET /products/_search
{
  "query": {
    "bool": {
      "should": [
        { "term": { "color": "black" } },
        { "term": { "brand": "Sony" } },
        { "range": { "rating": { "gte": 4 } } }
      ],
      "minimum_should_match": 2
    }
  }
}

A document must satisfy at least 2 of the 3 should conditions to appear in results.

Nested Bool Queries

Bool queries nest inside other bool queries for complex logic — like building a sentence with parentheses:

Find: (Sony OR Bose) AND (under ₹5000 OR rated above 4.5)

GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "bool": {
            "should": [
              { "term": { "brand": "Sony" } },
              { "term": { "brand": "Bose" } }
            ],
            "minimum_should_match": 1
          }
        },
        {
          "bool": {
            "should": [
              { "range": { "price": { "lte": 5000 } } },
              { "range": { "rating": { "gte": 4.5 } } }
            ],
            "minimum_should_match": 1
          }
        }
      ]
    }
  }
}

Filter Context vs Query Context

Query Context (must, should):
  - Elasticsearch calculates a relevance score
  - Results are sorted by score (best match first)
  - Slower — scoring takes computation

Filter Context (filter, must_not):
  - No relevance score calculated
  - Results are either in or out (binary)
  - Faster — results are cacheable
  - Best for: exact values, ranges, dates, booleans

Practical Tip: Always Filter First

Put all non-text conditions inside filter and only your actual search text inside must. This pattern makes Elasticsearch cache your filters, dramatically speeding up repeated similar queries — for example, when many users browse the same category with different search terms.

Leave a Comment