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.

Note

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 with index.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 buckets and 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 with step. Requires a known time range, either by setting start and end explicitly 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, and buckets. 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 as max(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 by grouping, such as sum 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 a without grouping, such as sum without (pod) (...), the remaining labels of each series are returned in a single _timeseries column 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 index explicitly instead of relying on the metrics-* default, to narrow the data scanned.
  • Omit range selectors, also when porting a Prometheus query: write rate(http_requests_total) instead of rate(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, or increase for counters, and functions such as avg_over_time or max_over_time, or the raw metric, for gauges. For native histograms, use increase instead of rate, and wrap the result in a histogram function, such as histogram_quantile(0.99, sum by (job) (increase(http_request_duration_seconds))).
  • In Kibana, omit start and end so the query follows the date picker. Elsewhere, set start and end explicitly, rather than only step, which covers all data in the index.
  • Filter by labels in the PromQL selector, such as network.cost{cluster!="prod"}, rather than with WHERE after PROMQL. Selector filters reduce the data read, and a later WHERE doesn'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 $__range variable, or its $__range_s and $__range_ms variants: replace the range selector, such as [$__range] or [${__range_s}s], with [?_tend - ?_tstart], and set time=?_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

Post-processing with ES|QL

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

Native histograms, stored as 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.

Change the limit with the 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).

One-to-one, many-to-one, and one-to-many matching between operands aggregated with 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, and stdvar, with by(...), without(...), or no grouping.
  • topk, bottomk, limitk, and limit_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", "(.*):.*") Use it inside 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 -