What Is the Query DSL?
Elasticsearch's Query DSL (Domain Specific Language) is a JSON-based syntax sent in the body of a search request to describe exactly which documents to find and how they should be ranked. Instead of writing SQL-like strings, you build a nested JSON tree of query clauses: leaf clauses that test a single field, and compound clauses that combine other clauses with boolean logic. Every request, from a simple keyword lookup to a multi-field aggregation, is expressed through this same DSL, which Elasticsearch parses and translates internally into Lucene queries against the inverted index.
Cricket analogy: Writing a Query DSL request is like filling out a DRS review form: you specify the exact clause — lbw, caught behind, bat-pad — rather than shouting a vague complaint, and the third umpire (Lucene) parses your structured request against recorded ball-tracking data.
Anatomy of a DSL Query
A typical search request wraps everything inside a top-level query object, which contains one query clause — often a compound clause like bool that nests further leaf clauses such as match, term, or range. Alongside query, the request body can include sort, size, from, _source filtering, and aggs for aggregations, all as sibling keys in the same JSON document. Because the DSL is just JSON, it composes naturally: any leaf or compound clause can be nested arbitrarily deep, letting you express complex logic like 'title matches laptop AND price is between 500 and 1500 AND NOT out_of_stock' as a single structured object.
Cricket analogy: A search request body is like a full match scorecard: the query clause is the ball-by-ball commentary nested inside, while sort and size sit alongside it like the innings summary and target — separate sections of one structured document.
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "laptop" } }
],
"filter": [
{ "range": { "price": { "gte": 500, "lte": 1500 } } },
{ "term": { "out_of_stock": false } }
]
}
},
"sort": [ { "price": "asc" } ],
"size": 20
}Leaf Queries vs Compound Queries
Leaf queries evaluate a single field against a single value and are the building blocks of every search: match for full-text analyzed search, term for exact unanalyzed matches, range for numeric or date bounds, and exists for field presence. Compound queries combine leaf queries or other compound queries using logical operators — bool being the workhorse, with must, should, must_not, and filter clauses — plus specialized compound types like dis_max for taking the best-scoring branch across alternatives. Understanding which leaf query fits a field's mapping type is essential, since running match against a keyword field or term against a text field produces silently wrong results.
Cricket analogy: A leaf query is a single delivery — an off-cutter or a yorker — while a compound query is the full over combining six such deliveries with a bowling strategy, the way bool combines individual match and term clauses.
Rule of thumb: use match, match_phrase, and multi_match against text fields for relevance-scored full-text search; use term, terms, and range against keyword, numeric, date, or boolean fields for exact filtering. Mixing them up is the single most common Query DSL mistake.
Query Context vs Filter Context
Every clause in the DSL runs in one of two contexts. In query context, Elasticsearch asks 'how well does this document match?' and computes a relevance _score using a similarity algorithm like BM25. In filter context, it asks only 'does this document match, yes or no?' with no scoring, and the results of filter clauses are cacheable bitsets that Lucene can reuse across repeated requests. Clauses placed inside must and should run in query context and affect scoring; clauses placed inside filter and must_not run in filter context, skip scoring entirely, and are typically faster and more cache-friendly for exact-value constraints like status fields, date ranges, or tenant IDs.
Cricket analogy: Query context is like a judged category in a talent contest scoring technique out of ten, while filter context is like an eligibility check — 'is the player under 19?' — a binary yes/no gate with no score attached.
Putting a term or range clause inside must instead of filter still works correctly, but it needlessly contributes to the relevance score and skips Lucene's filter cache, which can meaningfully slow down high-volume queries with static constraints.
- The Query DSL is a JSON tree of leaf and compound clauses sent in a search request body.
- Leaf queries (match, term, range, exists) test a single field; compound queries (bool, dis_max) combine other queries.
- The request body's query key sits alongside sibling keys like sort, size, and aggs.
- match/match_phrase are for analyzed text fields; term/terms are for exact keyword, numeric, or boolean fields.
- Query context (must, should) computes a relevance _score; filter context (filter, must_not) is a binary yes/no with no scoring.
- Filter context clauses are cacheable by Lucene and are generally faster for exact, repeatable constraints.
- Misplacing exact-match clauses in query context instead of filter context is a common performance mistake.
Practice what you learned
1. Which clause should you use to check whether a keyword field exactly equals 'shipped'?
2. What is the main practical difference between query context and filter context?
3. Inside a bool query, where should a static range filter on a date field typically be placed for best performance?
4. What happens if you run a match query against a keyword-mapped field?
Was this page helpful?
You May Also Like
Match and Term Queries
How Elasticsearch's two most fundamental leaf queries differ: match for analyzed full-text search and term for exact, unanalyzed matching.
Bool Queries
How the bool compound query combines must, should, must_not, and filter clauses to express complex search logic in Elasticsearch.
Filtering vs Scoring
Why the choice between filter context and query context is a core Elasticsearch performance and relevance decision, not just a syntax preference.