The Elasticsearch range query returns documents whose values for a given field fall inside the supplied bounds. It works against date, numeric (long, double, scaled_float, etc.), ip, and keyword fields, with gt, gte, lt, and lte operators. For range data types (integer_range, date_range, ...) the optional relation parameter controls how query and field ranges overlap. Range queries belong in a filter context whenever you do not need their score.
Syntax
GET /index/_search
{
"query": {
"range": {
"field_name": {
"gte": "lower_bound",
"lte": "upper_bound",
"format": "yyyy-MM-dd",
"time_zone": "+01:00"
}
}
}
}
Parameters
| Parameter | Description | Required | Default |
|---|---|---|---|
gt |
Greater than. | No | - |
gte |
Greater than or equal to. | No | - |
lt |
Less than. | No | - |
lte |
Less than or equal to. | No | - |
format |
Date format used to parse gt/gte/lt/lte for date fields. Overrides the mapping format. |
No | Mapping's format |
time_zone |
UTC offset (+01:00) or IANA zone (Europe/Berlin) applied to date bounds. Not applied to now or to dates that already include an offset. |
No | UTC |
relation |
Overlap mode for *_range field types: INTERSECTS, CONTAINS, WITHIN. |
No | INTERSECTS |
boost |
Score multiplier. | No | 1.0 |
At least one of gt/gte/lt/lte is required. Bounds may also be omitted to produce an unbounded one-sided range.
Examples
Numeric range on a price field, used as a filter:
GET /products/_search
{
"query": {
"bool": {
"filter": [
{ "range": { "price": { "gte": 50, "lte": 100 } } }
]
}
}
}
Date range with date math (now, now-7d/d) and a timezone:
GET /logs-*/_search
{
"query": {
"range": {
"@timestamp": {
"gte": "now-7d/d",
"lte": "now/d",
"time_zone": "+02:00"
}
}
}
}
Range on a date_range field with relation:
GET /events/_search
{
"query": {
"range": {
"event_window": {
"gte": "2026-01-01",
"lte": "2026-01-31",
"relation": "CONTAINS"
}
}
}
}
Unbounded upper range on an ip field:
GET /access-logs/_search
{
"query": {
"range": {
"client_ip": { "gte": "10.0.0.0" }
}
}
}
Performance and Use Notes
Range queries on numeric, date, and IP fields are backed by BKD trees and are fast even on very large indices. The cost grows with the breadth of the range and the number of segments scanned, not with the cardinality of the field. Wrap range predicates in a bool.filter clause so the query node skips scoring and lets the result enter the per-node query cache.
Range queries on keyword and text fields use lexicographic order over the term dictionary. They are correct but rarely what callers want - prefer numeric or date types. Date math is evaluated on the coordinating node; expressions like now-1d/d round to the day in UTC unless time_zone is set. now is not cached because it is non-deterministic.
Range filters frequently dominate the cost of analytics dashboards and log search. Pulse tracks slow query patterns across your Elasticsearch clusters, highlights range queries that miss the BKD or query cache, and recommends concrete mapping or shard-routing fixes.
Common Mistakes
- Putting a range predicate in the
mustclause when no score is needed - this skips the query cache and wastes scoring CPU. Move it tofilter. - Sending date strings in a format the mapping does not declare. Either fix the mapping or set
formatexplicitly on the query. - Assuming
relation: WITHINis the default for*_rangefields. The default isINTERSECTS. - Using
nowrather thannow/d(ornow/h) for dashboards - the unrounded value defeats caching. - Running ranges against an analyzed
textfield instead of its.keywordsub-field, getting lexicographic surprises like"10" < "2".
Frequently Asked Questions
Q: What is the difference between gt and gte in an Elasticsearch range query?
A: gt is strict greater-than, so the bound is excluded; gte is greater-than-or-equal and includes the bound. The same pattern applies to lt (exclusive) versus lte (inclusive). Mixing is fine, e.g. gte: 0, lt: 100.
Q: How do I write an unbounded range query?
A: Omit the side you do not need. { "range": { "price": { "gte": 100 } } } matches any price at or above 100 with no upper limit. Both gt/gte and lt/lte are independent.
Q: How does the range query handle missing values?
A: Documents missing the field are not matched. The range query has no missing parameter - if you need to include them, combine the range with a bool.should that uses a must_not exists query, or wrap with constant_score.
Q: Can I use a range query on a date field with timezone offsets?
A: Yes. Set time_zone to a UTC offset or IANA zone, and Elasticsearch converts the gte/lte strings before matching. time_zone does not apply to now or to bounds that already carry an offset.
Q: When should I use a term query instead of a range query?
A: Use a term query for exact equality on a single value, and a range query for any inequality (open or closed interval). Both run cheaply in filter context, but range queries are required as soon as you have >, <, between, or date math.
Q: How does relation work for range field types?
A: When the indexed field is itself a range (e.g. date_range), relation defines overlap semantics. INTERSECTS (the default) matches if the two intervals share any point, CONTAINS if the query range is fully inside the indexed range, and WITHIN if the indexed range is fully inside the query range.
Related Reading
- Elasticsearch Query Language: overview of the query DSL.
- Terms Query: exact match against one or more values.
- Bool Query: combine range filters with other predicates.
- Exists Query: match or exclude documents based on field presence.
- Nested Query: apply ranges inside nested objects.