ES|QL PROMQL command
The PROMQL source command queries time series indices using Prometheus Query Language (PromQL).
Like TS, it enables time series aggregation functions, but accepts PromQL syntax instead of ES|QL.
PROMQL supports most, but not all, of PromQL. Refer to Prometheus compatibility for unsupported constructs and differences from Prometheus, and to PromQL functions for the supported functions and their restrictions.
The PROMQL command accepts zero or more space-separated <option>=<value> pairs, followed by a PromQL expression in parentheses that can be prefixed with a result name.
PROMQL [ <option>=<value> ... ] [ <result_name>= ](<PromQL Expression>)
The options are inspired by the Prometheus HTTP API with some additions specific to ES|QL.
index- A list of indices, data streams, or aliases. Supports wildcards and date math.
Defaults to
metrics-*querying matching indices withindex.mode: time_series. Example:PROMQL index=metrics-*.otel-* http_rate=(sum(rate(http_requests_total))) step- Query resolution step width (optional).
Automatically determined given the number of target
bucketsand the selected time range. Example:PROMQL step=1m http_rate=(sum(rate(http_requests_total))) buckets- Target number of buckets for auto-step derivation.
Defaults to
100. Mutually exclusive withstep. Requires a known time range, either by settingstartandendexplicitly or implicitly through Kibana's time range filter. Example:PROMQL buckets=50 start="2026-04-01T00:00:00Z" end="2026-04-01T01:00:00Z" http_rate=(sum(rate(http_requests_total))) start- Start time of the query, inclusive (optional).
Uses the start based on Kibana's date picker if missing. Set together with
end. Refer to Time range for queries without a time range. Example:PROMQL start="2026-04-01T00:00:00Z" end="2026-04-01T01:00:00Z" http_rate=(sum(rate(http_requests_total))) end- End time of the query, inclusive (optional).
Uses the end based on Kibana's date picker if missing. Set together with
start, and not before it. Refer to Time range for queries without a time range. Example:PROMQL start="2026-04-01T00:00:00Z" end="2026-04-01T02:00:00Z" http_rate=(sum(rate(http_requests_total))) time-
Evaluation time of an instant query (optional). Evaluates the expression once, at this time, instead of at each step of a range query. Mutually exclusive with start,end,step, andbuckets. Example:PROMQL time="2026-04-01T01:00:00Z" http_rate=(sum(rate(http_requests_total))) scrape_interval- The expected metric collection interval.
Defaults to
1m. Used to determine implicit range selector windows asmax(step, scrape_interval). Example:PROMQL scrape_interval=15s http_rate=(sum(rate(http_requests_total))) <result_name>=(<PromQL Expression>)- Name of the output column with the query result timeseries (optional).
By default, the name of the output column is the PromQL expression itself.
Example:
PROMQL http_rate=(sum by (instance) (rate(http_requests_total))) | SORT http_rate DESC
start, end, and time accept an RFC 3339 timestamp with a UTC offset, such as "2026-04-01T00:00:00Z",
or a Unix timestamp in seconds, which can be fractional, such as 1775001600. Date math, such as now-1h,
isn't supported. In Kibana, the ?_tstart and ?_tend parameters reference the time range of the date picker,
such as time=?_tend.
step and scrape_interval accept a PromQL duration, such as 30s or 5m, or a number of seconds.
The PROMQL command takes standard PromQL parameters and a PromQL expression, runs the query, and returns the
results as regular ES|QL columns. You can continue to process the columns with other ES|QL commands.
A range query needs either step, or both start and end, from which the step is derived using buckets.
In Kibana, the date picker provides start and end. Without a time range and without step, the query fails.
With step but no time range, the query covers all data in the index.
The result contains the following columns:
| Column | Type | Description |
|---|---|---|
The PromQL expression (or <result_name> if specified) |
double |
The computed metric value |
step |
date |
The timestamp for each evaluation step. For an instant query, the evaluation time |
| Grouping labels (if any) | keyword |
One column per grouping label from by clauses |
_timeseries (if any) |
keyword |
The labels of each series as a JSON string |
The label columns depend on the outermost aggregation of the PromQL expression:
- With a
bygrouping, such assum by (instance) (...), each grouping label gets its own output column. - With an aggregation without grouping, such as
sum(...), there are no label columns, and the result is a single series. - Without a cross-series aggregation, such as
rate(http_requests_total), or with awithoutgrouping, such assum without (pod) (...), the remaining labels of each series are returned in a single_timeseriescolumn as a JSON string.
A range query returns one row per series and evaluation step. An instant query returns one row per series.
The index parameter accepts the same patterns as FROM and TS, including wildcards and comma-separated lists.
If omitted, it defaults to metrics-*, which queries matching indices configured with
index.mode: time_series.
The Prometheus-compatible query and query_range endpoints use the same default when the {index} path parameter is omitted.
In standard PromQL, functions like rate require a range selector: rate(http_requests_total[5m]).
The PROMQL command allows omitting the range selector entirely. When the range selector is absent, the window is
determined automatically as max(step, scrape_interval).
For example: PROMQL scrape_interval=15s http_rate=(sum(rate(http_requests_total))).
An implicit window adapts to the time range and step. A fixed window doesn't: when it's shorter than the step,
such as [5m] with a step of 1h, each step only reflects the last 5 minutes and ignores the samples in between.
A fixed window is only useful when you need exactly that window, such as the rate over the last 5 minutes.
A duration, such as in a range selector, can be the difference of two RFC 3339 timestamp parameters. For example,
[?_tend - ?_tstart] spans the time range of the date picker in Kibana.
- Set
indexexplicitly instead of relying on themetrics-*default, to narrow the data scanned. - Omit range selectors, also when porting a Prometheus query: write
rate(http_requests_total)instead ofrate(http_requests_total[5m]), so the window adapts to the time range and step. Refer to Implicit range selectors. The exception is a value over the whole time range. Refer to Single-value results. - Name the result, such as
http_rate=(...). Otherwise, the value column is named after the expression text, which changes whenever the expression is reformatted, so later commands can't reliably reference it. - Match the function to the metric type: use
rate,irate, orincreasefor counters, and functions such asavg_over_timeormax_over_time, or the raw metric, for gauges. For native histograms, useincreaseinstead ofrate, and wrap the result in a histogram function, such ashistogram_quantile(0.99, sum by (job) (increase(http_request_duration_seconds))). - In Kibana, omit
startandendso the query follows the date picker. Elsewhere, setstartandendexplicitly, rather than onlystep, which covers all data in the index. - Filter by labels in the PromQL selector, such as
network.cost{cluster!="prod"}, rather than withWHEREafterPROMQL. Selector filters reduce the data read, and a laterWHEREdoesn't.
A range query returns a value for every step. To get a single value per series, such as for a metric or gauge chart
or a ranking, use an instant query. In Kibana, set time=?_tend to evaluate the
expression at the end of the time range of the date picker
(refer to time range parameters).
For the current value, such as the current request rate, omit the range selector:
PROMQL index=metrics-generic.prometheus-* time=?_tend http_rate=(sum(rate(http_requests_total)))For a value over the whole time range, such as the total number of requests, use a range selector that spans the time range,
[?_tend - ?_tstart]: PROMQL index=metrics-generic.prometheus-* time=?_tend total_requests=(sum(increase(http_requests_total[?_tend - ?_tstart])))This is also how to port a Grafana query that uses the
$__rangevariable, or its$__range_sand$__range_msvariants: replace the range selector, such as[$__range]or[${__range_s}s], with[?_tend - ?_tstart], and settime=?_tend. Refer to Time range durations.
Rely on Kibana's date picker for the time range, and let step and range selectors be inferred automatically:
PROMQL index=metrics-generic.prometheus-* http_rate=(sum(rate(http_requests_total)))
This is the recommended pattern for Kibana dashboards. The query responds to the date picker, adjusts the step size to the selected time range, and sizes the range selector window accordingly.
Evaluate the expression once, for example to get the current value of a metric:
PROMQL index=metrics-generic.prometheus-*
time="2026-04-01T01:00:00Z"
http_rate=(sum(rate(http_requests_total)))
PROMQL index=k8s step=1h result=(sum by (cluster) (network.cost))
| SORT result
| result:double | step:datetime | cluster:keyword |
|---|---|---|
| 15.875 | 2024-05-10T00:00:00.000Z | staging |
| 18.625 | 2024-05-10T00:00:00.000Z | prod |
| 26.5 | 2024-05-10T00:00:00.000Z | qa |
PROMQL index=k8s step=1h cost=(max by (cluster) (network.total_bytes_in{cluster!="prod"}))
| SORT cluster
| cost:double | step:datetime | cluster:keyword |
|---|---|---|
| 10797.0 | 2024-05-10T00:00:00.000Z | qa |
| 7403.0 | 2024-05-10T00:00:00.000Z | staging |
Pipe PromQL results into ES|QL commands for further aggregation:
PROMQL index=k8s step=1h bytes=(max by (cluster) (network.bytes_in))
| STATS max_bytes=MAX(bytes) BY cluster
| SORT cluster
| max_bytes:double | cluster:keyword |
|---|---|
| 931.0 | prod |
| 972.0 | qa |
| 238.0 | staging |
For queries outside Kibana, set start and end explicitly. The step and range selector are still inferred
automatically from the time range and the default buckets count:
PROMQL index=metrics-generic.prometheus-*
start="2026-04-01T00:00:00Z"
end="2026-04-01T01:00:00Z"
http_rate=(sum(rate(http_requests_total)))
Join PromQL results with external data using ES|QL commands:
PROMQL index=metrics-generic.prometheus-*
http_rate=(sum by (instance) (rate(http_requests_total)))
| LOOKUP JOIN instance_metadata ON instance
This section lists the differences from Prometheus 3.x.
Sections follow Querying basics in the Prometheus documentation.
In the examples on this page, h is a native histogram metric.
Supported
exponential_histogram fields.
histogram_quantile, histogram_count, histogram_sum, histogram_avg, increase, and sum support them, such as histogram_quantile(0.9, sum by (job) (increase(h[5m]))).
histogram_fraction and changes support them too.
Classic histograms, stored as _bucket series with an le label, such as histogram_quantile(0.9, sum by (le) (rate(x_bucket[5m]))).
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 1 | Native histogram as the result
|
h, increase(h[5m]) |
Wrap it in a histogram function, such as histogram_count |
#152263 |
Supported
Range vectors as function arguments, such as rate(x[5m]).
In the PROMQL command, the range can be left out, such as rate(x).
Refer to Implicit range selectors.
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 2 | Range vector as the result of an instant query | x[5m] |
- | #164220 |
Supported
String literals in single quotes, double quotes, or backticks.
Float literals, including scientific notation, hexadecimal, Inf, and NaN, such as 1e3 and 0x1f.
Time durations, such as 1h30m, and duration arithmetic, such as x[5m + 1m].
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 3 | step(), range(), min_of(), and max_of() in a duration (Prometheus: promql-duration-expr feature flag) |
x[step()], x[min_of(5m, step())] |
Use a fixed duration | #164221 |
Supported
A metric name with the label matchers =, !=, =~, and !~, such as http_requests_total{job="api", code=~"5.."}.
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 4 | Selector without a metric name | {job="api"} |
- | #146977 |
| 5 | Regex matcher on __name__ |
{__name__=~"node_.*"} |
Combine the metrics with or: node_cpu or node_mem |
#146977 |
Behaves differently
| # | Construct | Example | Elasticsearch | Prometheus | Issue |
|---|---|---|---|---|---|
| 6 | Negative matcher on __name__ |
{__name__!="up", job="node"} |
No results | Every other matching metric | #146977 |
Supported
A range selector with a duration, such as x[5m] or x[1h30m].
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 7 | anchored and smoothed modifiers (Prometheus: promql-extended-range-selectors feature flag) |
increase(x[5m] anchored) |
- | #164222 |
Supported
offset on instant and range selectors, such as x offset 5m and rate(x[5m] offset 1h).
Behaves differently
| # | Construct | Example | Elasticsearch | Prometheus | Issue |
|---|---|---|---|---|---|
| 8 | Negative offset at the end of the query range | x offset -5m |
No value at the last steps. Samples after the query end aren't read | Reads samples after the query end | - |
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 9 | @ modifier |
x @ 1700000000, x @ end() |
- | #164139 |
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 10 | Subquery | max_over_time(rate(x[5m])[1h:1m]) |
- | #164139 |
Supported
A selector without a range, such as http_requests_total, returns the latest sample of each series within the lookback window at each evaluation time.
Behaves differently
| # | Construct | Example | Elasticsearch | Prometheus | Issue |
|---|---|---|---|---|---|
| 11 | Lookback in instant queries | /api/v1/query |
Five minutes, fixed | Five minutes by default, configurable with lookback_delta |
#149939 |
| 12 | Lookback in range queries | /api/v1/query_range |
One step. A series disappears at the first step without its own sample |
Five minutes. The last sample repeats until then | - |
| 13 | Staleness markers | - | Ignored. A series disappears once its samples leave the lookback window | A series disappears at its staleness marker | - |
Sections follow Operators in the Prometheus documentation. Each operator reference entry lists further differences.
Supported
+, -, *, /, %, and ^ between scalars, between an instant vector and a scalar, such as rate(x[5m]) * 60, and between two instant vectors.
Vector matching lists which pairs of instant vectors match.
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 14 | Native histogram operands
|
h * 2, h1 + h2 |
- | #152263 |
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 15 | atan2 |
x atan2 y |
- | #164153 |
Supported
==, !=, >, <, >=, and <= between an instant vector and a number, such as rate(x[5m]) > 100, with or without bool, such as x > bool 100.
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 16 | Filter with a scalar as the first operand | 100 < x, time() <= x |
Swap the operands: x > 100 |
#158184 |
| 17 | Non-literal scalar as the second operand | x > time(), x > scalar(y) |
- | #158184 |
| 18 | Two vectors, or bool with a scalar as the first operand |
x > y, 100 > bool x |
For bool, swap the operands: x < bool 100 |
#158184 |
| 19 | Comparison inside another expression | (x > 100) + 1, count(x > 100) |
- | #158184 |
Supported
or between instant vectors, such as rate(a[5m]) or rate(b[5m]).
An or chain takes at most 8 operands by default.
esql.query.max_branch_count_per_merge cluster setting.
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 20 | and, unless |
a and b, a unless b |
- | #158184 |
| 21 | or with on(...) or ignoring(...) |
a or on(job) b |
- | #158184 |
| 22 | or inside another expression |
(sum(a) or sum(b)) * 2, count(a or b) |
- | #158184 |
Supported
Arithmetic between unaggregated metrics stored together in the same time series, such as network.bytes_in / network.connections, without explicit vector matching modifiers.
Arithmetic between operands aggregated without grouping, such as sum(a) / sum(b).
by(...), including on, ignoring, group_left, and group_right.
For example: sum by (instance) (a) / on(instance) sum by (instance) (b).
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 23 | Different unaggregated metrics stored as separate series with a __name__ dimension, such as data ingested with Prometheus remote write
|
a / b |
Aggregate both by the labels to match on: sum by (instance) (a) / sum by (instance) (b) |
#158184 |
| 24 | on, ignoring, group_left, or group_right with an operand that isn't aggregated |
a / on(instance) b, a * on(pod) group_left(node) b |
Aggregate both operands by the labels to match on | #157669 |
| 25 | Operands aggregated differently
|
sum(a) / b, b / scalar(sum(a)), topk(2, a) * b |
Aggregate both operands alike: sum(a) / sum(b). A number, such as sum(a) * 2, and or are supported |
#158184 |
| 26 | Nested aggregation next to an operand that reads a metric
|
count(count by (pod) (a)) / sum(b) |
- | #158184 |
| 27 | A different offset per operand
|
rate(x[5m]) / rate(x[5m] offset 1h) |
or is supported |
#158184 |
| 28 | label_replace or label_join as an operand |
label_replace(a, ...) * on(pod) group_left(node) b |
- | #157672 |
| 29 | fill(), fill_left(), and fill_right() (Prometheus: promql-binop-fill-modifiers feature flag) |
a + fill(0) b |
- | #164223 |
Behaves differently
| # | Construct | Example | Elasticsearch | Prometheus | Issue |
|---|---|---|---|---|---|
| 30 | Different unaggregated metrics stored as separate series
|
a / b |
No results | Series matched on their other labels | #158184 |
Supported
sum,avg,min,max,count,quantile,stddev, andstdvar, withby(...),without(...), or no grouping.-
topk,bottomk,limitk, andlimit_ratio.
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 31 | group, count_values |
group by (job) (x), count_values("v", x) |
- | #164009, #136248 |
| 32 | topk, bottomk, limitk, or limit_ratio with without(...) |
topk without (instance) (1, x) |
Use by(...) |
#157671 |
| 33 | Parameter that isn't a number literal | topk(scalar(a), b), quantile(scalar(a), b) |
Use a number | #164225 |
| 34 | Nested without(...) |
sum without (pod) (max without (container) (x)) |
Use by(...) |
#157671 |
| 35 | without(...) as an operand |
sum without (method) (x) * 2 |
Use by(...) |
#145308 |
| 36 | Grouping by a label named step |
sum by (step) (x) |
step is an output column of the PROMQL command |
- |
Behaves differently
| # | Construct | Example | Elasticsearch | Prometheus | Issue |
|---|---|---|---|---|---|
| 37 | quantile |
quantile(0.9, x) |
Approximate, using t-digest | Exact, with linear interpolation | - |
| 38 | limitk, limit_ratio
|
limitk(10, x) |
A different, equally valid subset | A deterministic pseudo-random subset, with the promql-experimental-functions feature flag |
- |
Rows follow Functions in the Prometheus documentation and are sorted by function name. Each function reference entry lists further differences.
Supported
The functions in the PromQL functions reference, such as rate, increase, histogram_quantile, label_replace, and the <aggregation>_over_time functions.
Not supported yet
| # | Construct | Example | Workaround | Issue |
|---|---|---|---|---|
| 39 | Functions listed under Not supported yet | absent(x), resets(x[1h]), info(x) |
- | #164001, #164003, #164002 |
| 40 | Parameter that isn't a number literal | clamp_min(a, scalar(b)), histogram_quantile(scalar(a), b) |
Use a number | #164225 |
| 41 | histogram_quantile or histogram_fraction over topk, bottomk, limitk, or limit_ratio
|
histogram_quantile(0.9, topk(5, a)) |
Aggregate with by(...) first: histogram_quantile(0.9, topk(5, sum by (job, le) (a))) |
#164226 |
| 42 | histogram_quantile or histogram_fraction over without(...)
|
histogram_quantile(0.9, sum without (instance) (rate(x[5m]))) |
Use by(...) |
#157671 |
| 43 | histogram_stddev, histogram_stdvar
|
histogram_stddev(h) |
- | #164152 |
| 44 | irate, delta on native histograms
|
irate(h[5m]) |
- | #152263 |
| 45 | label_replace or label_join outside by(...) |
label_replace(x, "dst", "$1", "instance", "(.*):.*") |
by(...): sum by (dst) (label_replace(...)) |
#157672 |
| 46 | label_replace or label_join over an aggregation
|
label_replace(sum by (job) (x), ...) |
- | #157672 |
| 47 | label_replace or label_join over anything other than a by(...) aggregation, or that overwrites a grouping label
|
label_replace(sum without (instance) (x), "dst", "$1", "job", "(.*)"), label_replace(sum by (job) (x), "job", "$1", "job", "(.*)") |
Aggregate with by(...) and write a new label: label_replace(sum by (job) (x), "dst", "$1", "job", "(.*)") |
#157672 |
| 48 | label_replace or label_join that writes a label named step
|
label_replace(x, "step", ...) |
step is an output column of the PROMQL command |
- |
| 49 | predict_linear with a horizon computed by a function
|
predict_linear(x[1h], time()), predict_linear(x[1h], scalar(y)) |
Use a number, a duration literal, or arithmetic on literals | #164225 |
| 50 | rate on native histograms
|
rate(h[5m]) |
Use increase: histogram_quantile(0.99, sum by (job) (increase(h[5m]))) |
#152263 |
| 51 | sort or sort_desc below the top level |
sum(sort(x)) |
Remove it. Sorting has no effect there | #164227 |
| 52 | step(), range(), start(), end() (Prometheus: promql-experimental-functions feature flag) |
time() - start() |
- | #164228 |
Behaves differently
| # | Construct | Example | Elasticsearch | Prometheus | Issue |
|---|---|---|---|---|---|
| 53 | absent_over_time with no data or over a gap |
absent_over_time(x{job="none"}[5m]) |
No results | 1, with the labels of the selector's equality matchers |
- |
| 54 | histogram_count, histogram_sum, or histogram_avg on classic buckets |
histogram_count(rate(x_bucket[5m])) |
An error. They accept only native histograms | No results | - |
| 55 | quantile_over_time |
quantile_over_time(0.9, x[5m]) |
Approximate, using t-digest | Exact, with linear interpolation | - |
| 56 | rate, increase |
rate(x[5m]) |
Computed over fixed time buckets, interpolated at bucket boundaries. Results can differ slightly | Extrapolated within each range window | - |