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 Limitations for unsupported constructs, to PromQL limitations for behavioral 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.
- 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. - 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 for the current value. 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):
PROMQL index=metrics-generic.prometheus-* time=?_tend http_rate=(sum(rate(http_requests_total)))
There's no reliable way yet to get a value over the whole time range, such as a total. Summing the steps of a range
query, such as with STATS SUM(...), overstates it when the step is shorter than scrape_interval, as the windows of
the steps overlap.
The majority of PromQL expressions run unchanged. The following constructs are not evaluated yet, so they return a client error (4xx):
- Binary set operators:
andandunless. -
Binary set operator or, except at the top level of an expression. A top-levelorchain supports at most 8 operands and can't useon(...)orignoring(...). A nestedor, a chain of more than 8 operands, or anorwithon(...)orignoring(...)returns a client error (4xx). - Comparison operators: evaluated only at the top level of an expression and only with a scalar literal on the right-hand side. Comparisons between two instant vectors, and nested comparisons, return a client error (4xx).
- Group modifiers:
on(...),ignoring(...),group_left,group_right. - The
@modifier. - Subqueries, such as
max_over_time(rate(http_requests_total)[1h:]). - Selectors without a metric name, and regex matchers on
__name__, such as{__name__=~"node_.*"}. - Binary expressions with a
without(...)aggregation as an operand, andwithout(...)aggregations nested inside anotherwithout(...)aggregation, such assum without (pod) (max without (container) (...)). Useby(...)instead. -
Binary expressions whose operands use different offsetvalues, such asrate(http_requests_total) / rate(http_requests_total offset 1h). This restriction doesn't apply toor. -
Binary expressions where both operands read metrics and one of them nests an aggregation inside another aggregation, such as count(count by (pod) (up)) / sum(machine_count). Nested aggregations on their own, such assum(sum by (pod) (...)), are supported. - Functions: refer to Not yet supported for the full list of recognized but unimplemented functions. Some supported functions have restrictions, which are listed under Differences from Prometheus on each function's reference entry.
-
Binary set operator or. -
The offsetmodifier.
The following constructs return no results instead of an error:
- Binary expressions between two operands that read different metrics without aggregating them, such as
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes. Unlike in Prometheus, the series of the two metrics aren't matched on their other labels. Aggregate both operands by the labels to keep instead, such asavg by (instance) (node_memory_MemAvailable_bytes) / avg by (instance) (node_memory_MemTotal_bytes).
For behavioral differences from Prometheus, refer to PromQL limitations.
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