Grafana Error Guide: 'input data must be a wide series but got type long' — Fix Alert Expressions
Fix Grafana unified alerting 'input data must be a wide series but got type long': add a reduce stage, fix the query frame format, and structure math/threshold expressions correctly.
- #grafana
- #observability
- #troubleshooting
- #errors
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Overview
Grafana’s unified alerting evaluates a rule as a pipeline of stages: a datasource query produces a data frame, and server-side expressions (reduce, math, threshold, resample) transform it into the single scalar-per-series that the alert condition needs. Each expression type expects its input in a specific frame format — “wide” (a time column plus one value column per series) versus “long” (a tall table with label columns and one value column). When a stage receives the wrong format, evaluation aborts with a type error and the rule shows “Error” instead of a real state.
The literal errors you will see in the alert rule’s state or Grafana’s log:
input data must be a wide series but got type long (input refid)
[sse.dataplane] error: input data must be a wide series but got type long
failed to execute conditions: input data must be a wide series but got type long (A)
The tell: the failing refid (e.g. A) points at the query or the stage feeding your math/threshold, and the words are “wide” vs “long” — a frame-format mismatch, not a query syntax error.
Symptoms
- An alert rule’s state is “Error”, not Alerting/OK, right after you build or import it.
- “Preview alert” or “Test rule” fails immediately with the wide/long message.
- SQL, Loki, CloudWatch Logs, or Elasticsearch queries feeding a
math/thresholdtrigger it more than Prometheus. - The same query renders fine in a panel but fails as an alert condition.
- Provisioned rules load but every evaluation errors.
Common Root Causes
1. Math/threshold reads a raw query instead of a reduced value
math, threshold, and classic_condition expect a reduced scalar per series (wide). Pointing them straight at a time-series query (or a long-format table) causes the type error.
2. A “long” frame from SQL / logs datasources
SQL, Loki, and log-based queries commonly return long frames (label columns + value). Alerting expressions want wide frames, so a reduce/format step is required first.
3. Missing reduce stage
The query returns a full time series; without a reduce (last/mean/max) collapsing it to one value per series, the downstream expression gets a series where it expects a number.
4. instant: false where an instant vector was intended
A range query returns many points per series (a series frame); the condition expects the latest value.
5. Multi-frame / mixed-shape query output
A query emitting multiple frames of differing shape (some wide, some long) makes the expression engine reject the input.
Diagnostic Workflow
Step 1: Find the failing refid and inspect the frame
In the rule editor, run each stage and read which refid errors. Then look at the raw frame the query returns:
# Query the datasource resource the way alerting does, and inspect the frame schema
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
http://localhost:3000/api/ds/query \
-d '{"queries":[{"refId":"A","datasourceUid":"prometheus-prod","expr":"up","instant":true}]}' \
| jq '.results.A.frames[].schema.meta.type, .results.A.frames[].schema.fields[].type'
A numeric wide frame is what expressions want; a timeSeriesLong/table shape needs reducing first.
Step 2: Add the missing reduce stage
Insert a reduce expression between the query and the math/threshold. In the UI this is “Reduce → Function: Last → Input: A”. As provisioning YAML:
data:
- refId: A
datasourceUid: prometheus-prod
relativeTimeRange: { from: 300, to: 0 }
model:
expr: sum(rate(http_requests_total{code=~"5.."}[5m]))
instant: false
- refId: B
datasourceUid: __expr__
model:
type: reduce
expression: A
reducer: last
- refId: C
datasourceUid: __expr__
model:
type: threshold
expression: B
conditions:
- evaluator: { type: gt, params: [1] }
The condition is C, fed by the reduced value B, not raw A.
Step 3: Convert long frames to wide (SQL / logs)
For SQL, shape the query so it returns one numeric value per series (a wide/numeric frame) — select an aggregate, not a raw table:
-- Returns a single reducible value per group, not a long table
SELECT service, avg(latency_ms) AS value
FROM requests
WHERE ts > now() - interval '5 minutes'
GROUP BY service
Then still add a reduce (Last) before the threshold if the frame carries a time dimension.
Step 4: Use an instant query where appropriate
For Prometheus alert conditions, an instant vector plus a reduce is the cleanest path:
model:
expr: sum by (job) (rate(http_requests_total{code=~"5.."}[5m]))
instant: true
Step 5: Verify the rule evaluates cleanly
# List rule state after saving; look for "Error" vs "Normal"/"Alerting"
curl -s -H "Authorization: Bearer $TOKEN" \
http://localhost:3000/api/prometheus/grafana/api/v1/rules \
| jq '.data.groups[].rules[] | {name, state, health}'
# Provisioning + alerting evaluation errors
journalctl -u grafana-server | grep -iE "ngalert|sse.dataplane|wide series"
Example Root Cause Analysis
An engineer imported an alert rule that queried sum(rate(http_requests_total{code=~"5.."}[5m])) and wired a threshold > 1 directly to the query’s refid A. The rule saved but every evaluation showed “Error” with input data must be a wide series but got type long (A).
Inspecting the frame via /api/ds/query showed the query returned a time-series frame (many points), while threshold expects one reduced value per series. There was no reduce stage — the threshold was reading a raw series where it needed a scalar.
Fix: insert a reduce (Function: Last) as refid B between the query and the threshold, and set the rule condition to the threshold refid C fed by B. The rule immediately evaluated to a real Normal/Alerting state. The root cause was a missing reduce stage, not the PromQL — the query itself was fine; its shape was wrong for the expression that consumed it.
Prevention Best Practices
- Always structure alert rules as query →
reduce→math/threshold, with the condition on the final expression, not the raw query; see more Grafana guides. - For SQL and log datasources, return an aggregated numeric value per series so frames are reducible, not long tables.
- Prefer instant queries for Prometheus alert conditions, then reduce.
- Test every rule with “Preview alert” before saving; a “wide series” error surfaces there instantly.
- When provisioning rules as code, template the reduce/threshold stages so authors can’t skip the reduce.
- Triage recurring alerting evaluation errors with the free monitoring assistant.
Quick Command Reference
# Inspect the frame type a query returns
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
http://localhost:3000/api/ds/query \
-d '{"queries":[{"refId":"A","datasourceUid":"<uid>","expr":"<query>","instant":true}]}' \
| jq '.results.A.frames[].schema.meta.type'
# Rule state after fixing (Error vs Normal/Alerting)
curl -s -H "Authorization: Bearer $TOKEN" \
http://localhost:3000/api/prometheus/grafana/api/v1/rules \
| jq '.data.groups[].rules[] | {name, state, health}'
# Evaluation errors in logs
journalctl -u grafana-server | grep -iE "ngalert|wide series"
Conclusion
“Input data must be a wide series but got type long” is a frame-format mismatch inside the alert expression pipeline. Fix it in order:
- Find the failing refid and inspect the frame the query returns (wide/numeric vs long).
- Insert a
reduce(Last/Mean) so the downstreammath/thresholdgets one value per series. - For SQL/log datasources, aggregate the query so it returns a reducible numeric frame.
- Point the rule
conditionat the final expression, not the raw query, and preview before saving.
Structuring every rule as query → reduce → threshold removes the cause; skipping the reduce is what produces the error.
More Grafana prompts & error guides
Every Grafana AI prompt and troubleshooting guide, in one place.
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.