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.