PromQL: Basics & selectors
|
This section documents the current Prometheus 3.x line (latest 3.14.0) as published at the Prometheus documentation and the current Grafana 13.x line (latest 13.2.1) as published at the Grafana documentation, which are the references these pages are written and verified against. No specific patch version is pinned. Some capabilities (Grafana Enterprise-only features, Grafana Cloud-hosted services, and long-term-storage systems like Thanos/Cortex/Mimir) are linked, not documented in depth. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, since both projects iterate quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
Every PromQL expression evaluates to one of a small set of value types, and almost every query in
this section’s remaining pages starts from the same two: an instant vector and a range vector.
This page covers that distinction, the label matchers used to select which series a query touches,
the offset and @ modifiers that shift a query in time, subqueries for computing a range-vector
function over another range, and the HTTP query API that the expression browser — and every
external client — issues these queries against. See
Querying basics for the full language
reference this page draws from.
Instant vectors vs. range vectors
A bare metric name, optionally narrowed with label matchers, is an instant vector: one sample per matching series, all as of the same evaluation timestamp.
http_requests_total{job="orders-api", status="500"}
Appending a range selector — a duration in square brackets — turns the same expression into a range vector: not one sample per series but every sample in that trailing time window, still one list per series.
http_requests_total{job="orders-api", status="500"}[5m]
A range vector cannot be graphed or returned as-is by an instant query; it exists to be fed into a
function that reduces it back to an instant vector, most commonly rate() or increase() over a
Counter:
rate(http_requests_total{job="orders-api", status="500"}[5m])
[5m] here means "the last 5 minutes of samples up to the query’s evaluation time," not a fixed
5-minute calendar bucket — a query re-run a second later slides the whole window forward by a
second. As a rule of thumb, pick a range at least four times the scrape interval so rate() always
has at least two samples to work with; see
PromQL: Functions for rate(), increase(), and
the rest of the function library that range vectors feed into.
Label matchers
Label matchers narrow an instant or range vector to the series whose label values satisfy every
matcher, AND-ed together. Four operators are available:
| Operator | Meaning |
|---|---|
|
Equals the given string, exactly. |
|
Does not equal the given string. |
|
Matches the given RE2 regular expression. |
|
Does not match the given RE2 regular expression. |
http_requests_total{job="orders-api", status="500"}
http_requests_total{job!="orders-api"}
http_requests_total{status=~"4..|5.."}
http_requests_total{handler!~"/health|/metrics"}
A regex match is always implicitly anchored (^(?:…)$), so status=~"5.." matches only
three-character values starting with 5 — not any value merely containing that substring. A bare
metric name such as http_requests_total is itself sugar for a name="http_requests_total"
matcher, which is why {name=~"http_requests_."} works as a matcher on the metric name itself
when no literal name is written before the braces. At least one matcher in a selector must be
non-empty (job=~"." alone is rejected) so a query can never silently select every series in the
TSDB. See
Time series
selectors for the complete matcher semantics, and
PromQL: Operators & Aggregation for
combining several selectors together with arithmetic, comparison, and set operators.
The offset modifier
offset shifts a selector’s evaluation window into the past by a fixed duration, without changing
the query’s own evaluation timestamp. It is the straightforward way to compare "now" against "same
time last week":
rate(http_requests_total[5m]) / rate(http_requests_total[5m] offset 1w)
offset applies to the selector it is written on, so in a compound expression each range or instant
vector can carry its own, independent offset. It composes with the @ modifier below but not with
subqueries applied to the same selector.
The @ modifier
@ pins a selector’s evaluation to a fixed Unix timestamp instead of the query’s own evaluation
time, which offset (a relative shift) cannot do on its own:
http_requests_total @ 1609746000
rate(http_requests_total[5m] @ 1609746000)
@ also accepts start() and end() inside a range-vector function evaluated by
/api/v1/query_range (see below), pinning every step of the range to the query’s own start/end
parameter rather than to that step’s own evaluation time — useful for a query that should compare
every point in a range against one fixed baseline. @, offset, and a range selector can all appear
on the same vector selector, in any order, and combine rather than override each other. See
the @ modifier for the full
grammar, including the start()/end() forms.
Subqueries
A subquery evaluates a range-vector function repeatedly over an inner range, at a given step,
producing a range vector out of what would otherwise be an instant-vector expression. The syntax
adds a second, inner resolution after the outer range: [<range>:<resolution>].
max_over_time(rate(http_requests_total[5m])[30m:1m])
Read from the inside out: rate(http_requests_total[5m]) is evaluated as if standalone, then
re-evaluated every 1m across the trailing 30m window, and max_over_time() reduces that series
of per-minute rate values down to one number — the highest 5-minute request rate seen in the last
half hour. Omitting the resolution ([30m:]) defaults it to the server’s global evaluation interval.
Reach for a subquery specifically when you need a range-vector function (max_over_time(),
increase(), deriv(), and similar) applied over another function’s output rather than over raw
samples — rate() itself never needs one, since it already accepts a plain range selector directly.
Subqueries are comparatively expensive: each inner step re-runs the whole inner expression, so prefer
a recording rule that pre-computes the
inner expression on a schedule when the same subquery is evaluated repeatedly (for example, from a
dashboard). See
Subqueries for the full
syntax and defaulting rules.
The HTTP query API
The expression browser, Grafana, and every other PromQL client all go through the same JSON HTTP
API rather than a special protocol. Two endpoints matter for reading data: /api/v1/query for a
single evaluation at one instant, and /api/v1/query_range for a series of evaluations across a
time window.
curl "http://localhost:9090/api/v1/query" \
--data-urlencode 'query=rate(http_requests_total{job="orders-api"}[5m])' \
--data-urlencode 'time=2025-09-06T10:00:00Z'
# https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries
query is the only required parameter; time (RFC 3339 or a Unix timestamp) defaults to the
server’s own current time when omitted. /api/v1/query_range instead requires start, end, and
step, and re-runs the query once per step across that window:
curl "http://localhost:9090/api/v1/query_range" \
--data-urlencode 'query=rate(http_requests_total{job="orders-api"}[5m])' \
--data-urlencode 'start=2025-09-06T09:00:00Z' \
--data-urlencode 'end=2025-09-06T10:00:00Z' \
--data-urlencode 'step=30s'
# https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries
Both return {"status":"success","data":{"resultType": "…", "result": […]}}; resultType is
vector for an instant query’s result and matrix for a range query’s, each sample serialized as
[<unix_timestamp>, "<value>"] (the value is a string so large or special floats such as NaN
round-trip exactly). step on /api/v1/query_range is independent of any range selector written
inside query itself — it only controls how often the whole expression is re-evaluated between
start and end, so a step coarser than an inner [5m] skips evaluation points rather than
widening the range. The full parameter list — including timeout, response codes for partial
results, and the metadata endpoints (/api/v1/series, /api/v1/labels, /api/v1/label/<name>/values) — is at HTTP API.
Continue with PromQL: Operators &
Aggregation for combining vectors with arithmetic, comparison, and by/without aggregation, and
PromQL: Functions for the function library — rate(), histogram_quantile(), and the rest — that range vectors and subqueries feed into.