Definition JSON reference
Every field of the dashboard definition document, and the rules that validate it
A dashboard definition is one JSON document. This page is its syntax — which fields exist and what gets rejected.
The editor, the create API, import, and repository sync all go through the same validator, so the rules here hold whichever path you use. The import screen runs the same validation before saving, so a hand-written document can simply be pasted there to check it.
Minimal example
One panel plotting request count per service over time.
{
"schemaVersion": 1,
"panels": [
{
"id": "throughput",
"title": "Throughput",
"layout": { "x": 0, "y": 0, "w": 6, "h": 6 },
"visualization": { "type": "TIME_SERIES" },
"queries": [
{
"refId": "A",
"signal": "traces",
"aggregation": { "function": "COUNT" },
"groupBy": ["service.name"],
"output": { "type": "TIME_SERIES" }
}
]
}
]
}variables, sections, and settings may be omitted — the server fills in empty arrays and defaults and stores the normalized document.
Top level
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion | integer | yes | Definition format version. Only 1 is understood today. |
variables | array | no | Values picked at the top of the screen. Defaults to [] |
sections | array | no | Groups of panels. Defaults to [] |
panels | array | no | The tiles. Defaults to [] |
settings | object | no | Default time range, refresh interval, timezone |
uid | string | no | Identifier that survives moving the document. Generated if absent. |
If schemaVersion is not 1, the response is a single UNSUPPORTED_SCHEMA_VERSION and nothing below it is inspected — an unknown format gives no basis for reading the rest.
settings
| Field | Allowed values | Default |
|---|---|---|
defaultTimeRange | LAST_30_MIN · LAST_1_HOUR · LAST_6_HOURS · LAST_24_HOURS · LAST_7_DAYS | LAST_1_HOUR |
refreshInterval | NONE · 10s · 30s · 1m · 5m | 30s |
timezone | BROWSER · UTC | BROWSER |
The execute API takes absolute timestamps (from, to). defaultTimeRange is what the screen selects when the dashboard is first opened.
variables
| Field | Type | Description |
|---|---|---|
name | string | Required, unique within the definition. Filters reference it as ${name}. |
label | string | Name shown on screen |
type | string | SERVICE · ATTRIBUTE · TEXT · CUSTOM |
source | object | ATTRIBUTE only — signal and field, plus metric for metrics |
defaultValue | string or array | Used when the request carries no selection |
multi | boolean | Allow selecting several values |
options | string array | CUSTOM only — the fixed choices |
Rules per type
| Type | Where values come from | Rules |
|---|---|---|
SERVICE | The workspace service catalog | No source needed. |
ATTRIBUTE | Attribute values actually collected | source.signal and source.field are required. When signal is metrics, source.metric is required too — labels are read per metric. |
TEXT | None (typed in) | Cannot carry source or options. |
CUSTOM | options | Needs at least one option and cannot carry source. |
How a selection reaches a filter
{
"variables": [
{ "name": "service", "label": "Service", "type": "SERVICE", "defaultValue": "ALL", "multi": true },
{ "name": "env", "label": "Environment", "type": "ATTRIBUTE",
"source": { "signal": "traces", "field": "environment" } },
{ "name": "keyword", "label": "Search", "type": "TEXT" },
{ "name": "tier", "label": "Tier", "type": "CUSTOM", "options": ["free", "pro"] }
],
"panels": [
{
"id": "errors", "layout": { "x": 0, "y": 0, "w": 6, "h": 6 },
"visualization": { "type": "TIME_SERIES" },
"queries": [
{
"refId": "A", "signal": "traces",
"aggregation": { "function": "COUNT" },
"filters": [
{ "field": "service.name", "operator": "IN", "value": "${service}" },
{ "field": "has_error", "operator": "EQ", "value": true }
],
"groupBy": ["service.name"],
"output": { "type": "TIME_SERIES" }
}
]
}
]
}| Situation | Result |
|---|---|
| No selection in the request | defaultValue is used. |
The value is ALL | Filters using that variable are dropped entirely — "all" means do not filter. |
A multi variable with several values | Even an EQ operator is promoted to IN; taking only the first value would quietly shrink the result. |
Several values on a non-multi variable | Rejected. |
An empty TEXT value | That filter is dropped — nothing typed yet is a normal state. |
An empty list selection (SERVICE, ATTRIBUTE, CUSTOM) | Rejected — there was something to pick and nothing was picked. |
The whole value must be the reference ${name}. Mixed forms such as prefix-${name} are rejected: string concatenation would strip the value of its type. Variable values must be strings — numbers and booleans are refused.
sections
| Field | Type | Description |
|---|---|---|
id | string | Required, unique within the definition. |
title | string | Group heading |
panelIds | string array | The id of each panel in this section. Pointing at a missing panel is UNKNOWN_REFERENCE. |
panels
| Field | Type | Description |
|---|---|---|
id | string | Required, unique within the definition. |
title | string | Panel heading |
layout | object | Grid position x, y, w, h |
visualization | object | type plus optional options |
queries | array | Required. Between 1 and 20. |
layout
| Rule | Detail |
|---|---|
| Grid width | 12 columns; x + w above 12 is OUT_OF_RANGE |
| Size | w and h must be at least 1 |
| Position | x and y cannot be negative |
| Height cap | h at most 24 |
visualization
The visualization does not decide the shape of the result. The query's output.type does, and validation only checks that the two are compatible.
visualization.type | Required output.type |
|---|---|
TIME_SERIES | TIME_SERIES |
NUMBER | SCALAR |
TABLE · BAR · PIE · LOGS · TRACES | TABLE |
HISTOGRAM | HISTOGRAM |
A mismatch is INCOMPATIBLE_OUTPUT. visualization.options is free-form JSON and is not validated.
queries
| Field | Type | Description |
|---|---|---|
refId | string | Required, unique within the panel. ratio points at this value. |
signal | string | traces · logs · metrics. Not allowed on a ratio query. |
source | object | The metric name for a metrics query |
aggregation | object | function, field, level |
filters | array | Conditions, all combined with AND |
groupBy | string array | Axes to split by. At most 5. |
output | object | Required — type is the result shape. |
alias | string | Name given to the series or value column |
records | boolean | Return raw rows instead of aggregating |
measures | array | Value columns of a table. At most 8. |
ratio | object | Divide two queries of the same panel |
limit | integer | Cap on series count; 0 means the default of 20, maximum 100 |
orderBy | object | Ordering of raw records. Only allowed on a records query. |
output.type
| Type | Shape | Conditions |
|---|---|---|
TIME_SERIES | time × series | Series come from groupBy. |
SCALAR | one value | Cannot have groupBy. |
TABLE | rows × columns | Needs at least one groupBy (except with records). |
HISTOGRAM | time × bucket | Only metrics can produce it, and it cannot carry groupBy or aggregation. |
When signal is metrics, source.metric is required — that field is the only place saying which metric is read.
filters
| Operator | Value | Fields it works on |
|---|---|---|
EQ | a single value | any field |
IN | a non-empty array | any field |
CONTAINS | a string | dictionary fields only (not attributes) |
BETWEEN | [min, max] — either side may be null | dictionary fields only (not attributes) |
NOT_EQ | a single value | any field |
NOT_IN | a non-empty array | any field |
NOT_CONTAINS | a string | dictionary fields only (not attributes) |
All filters in the list are combined with AND. There is no OR; choosing among several values is IN.
The negated operators exist so that "everything except this" can be written into the definition — slow requests excluding streaming ones, for instance, where working around it with a BETWEEN upper bound would throw away the genuinely slow requests too.
Attribute fields (the resource., span., log. prefixes and metric labels) accept EQ, IN, NOT_EQ, and NOT_IN. Substring matching (CONTAINS, NOT_CONTAINS) and ranges (BETWEEN) are not opened there because they cannot use the index. Values must be strings. The constraint is enforced at execution, not at save time — the definition saves and only the panel fails.
Field names
Field names resolve against a per-signal dictionary. A name that is not in it is rejected with UNKNOWN_FIELD, so that a typo cannot produce a dashboard that saves and then quietly fails to draw.
traces field | Meaning |
|---|---|
service.name · service.version · environment | Service and deployment environment |
trace_id · span_id · parent_span_id | Identifiers |
name (= span_name) | Span name |
kind | Span kind |
status_code | Span status |
has_error | Error verdict, written as true / false |
duration_ms | Duration in milliseconds — the field numeric aggregations use |
logs field | Meaning |
|---|---|
service.name · service.version · environment | Service and deployment environment |
trace_id · span_id | Linked trace |
level (= severity) | Severity, compared case-insensitively |
body | Log body |
scope_name · event_name | Scope and event that produced the line |
metrics field | Meaning |
|---|---|
service.name · service.version · environment | Service and deployment environment |
metric | Metric name |
| any other name | Read as a metric label — no dictionary entry needed |
Attributes outside the dictionary are reached through prefixes.
| Prefix | Signals | Points at |
|---|---|---|
resource. | traces · logs · metrics | Resource attributes — e.g. resource.k8s.pod.name |
span. | traces | Span attributes — e.g. span.http.route |
log. | logs | Log attributes — e.g. log.request_id |
Only dictionary fields can be the argument of an aggregation (aggregation.field, measures[].field). Prefixed attributes are strings, so they cannot feed sum, average, or a quantile, and are rejected with UNKNOWN_FIELD. resource.workspace_id and resource.retention_days cannot be queried.
To see which fields and values actually arrived, use the filter on the Logs, Traces, and Metrics screens — whatever autocomplete offers is what a definition may use.
aggregation
| Signal | Available functions |
|---|---|
traces · logs | COUNT SUM AVG MIN MAX UNIQ P50 P75 P90 P95 P99 QUANTILE |
metrics | COUNT SUM AVG MIN MAX RATE INCREASE P50 P90 P95 P99 |
| Field | Description |
|---|---|
function | A name from the table above. Lowercase is accepted and normalized to uppercase on save. |
field | The field to aggregate. COUNT does not need one. |
level | QUANTILE only. Must be strictly between 0 and 1 — e.g. 0.999 |
Which functions actually work on a metric depends on its type (gauge, sum, histogram). Validation only rejects names outside the list; a combination that does not fit the metric type surfaces at execution.
measures — tables with several value columns
A table like "requests, p50, p95 per endpoint" is built with measures. One query, one scan, values taken from the same rows, so the axes cannot drift apart.
{
"id": "by-route",
"title": "Requests by route",
"layout": { "x": 0, "y": 0, "w": 12, "h": 8 },
"visualization": { "type": "TABLE" },
"queries": [
{
"refId": "A",
"signal": "traces",
"filters": [{ "field": "service.name", "operator": "IN", "value": "${service}" }],
"groupBy": ["span.http.route"],
"measures": [
{ "function": "COUNT", "alias": "requests" },
{ "function": "P50", "field": "duration_ms", "alias": "p50" },
{ "function": "P95", "field": "duration_ms", "alias": "p95" }
],
"output": { "type": "TABLE" }
}
]
}| Rule | Detail |
|---|---|
| Result shape | TABLE only |
With aggregation | Not allowed — a single value column uses aggregation instead. |
With records | Not allowed — raw rows have no value columns. |
| Count | At most 8 |
| Column name | alias; without one the function name is used, and a collision is DUPLICATE. |
records — raw rows
Returns log or trace rows as they are, without aggregating. Seeing "5 errors" is only useful if you can then read those five lines.
{
"id": "recent",
"title": "Recent logs",
"layout": { "x": 0, "y": 0, "w": 12, "h": 10 },
"visualization": { "type": "LOGS" },
"queries": [
{
"refId": "A",
"signal": "logs",
"records": true,
"filters": [{ "field": "level", "operator": "EQ", "value": "error" }],
"output": { "type": "TABLE" },
"alias": "logs"
}
]
}| Rule | Detail |
|---|---|
| Signal | logs and traces only — metrics have no raw row. |
| Result shape | TABLE only |
aggregation, groupBy, ratio | Cannot be combined with it. |
| Rows | 200 on screen; the panel pages on to the rest when there is more. |
| Default order | Logs by timestamp descending, traces by duration_ms descending |
| Changing the order | Pick it with orderBy. |
orderBy — ordering the raw table
orderBy looks like { "field": "timestamp", "desc": true }. Only columns carried by that table can be chosen — ordering by a column nobody can see leaves the screen unable to explain the result.
| Signal | Orderable columns |
|---|---|
logs | timestamp · service · level · body · trace_id |
traces | timestamp · service · name · duration_ms · status_code · trace_id |
This is what makes a panel like "recent error traces" possible; with the default order alone you only ever get the slowest ones.
orderBy on a query that is not records is rejected with INVALID_VALUE — the order of an aggregated result is already fixed to value descending. Pointing at a column the table does not carry is UNKNOWN_FIELD.
ratio — division
Divides two queries of the same panel. There is exactly one shape, "A ÷ B", which is what error rate and success rate need.
{
"id": "error-rate",
"title": "Error rate",
"layout": { "x": 0, "y": 0, "w": 3, "h": 4 },
"visualization": { "type": "NUMBER" },
"queries": [
{ "refId": "A", "output": { "type": "SCALAR" },
"ratio": { "numerator": "B", "denominator": "C" }, "alias": "error rate" },
{ "refId": "B", "signal": "traces", "aggregation": { "function": "COUNT" },
"filters": [{ "field": "has_error", "operator": "EQ", "value": true }],
"output": { "type": "SCALAR" } },
{ "refId": "C", "signal": "traces", "aggregation": { "function": "COUNT" },
"output": { "type": "SCALAR" } }
]
}| Rule | Detail |
|---|---|
| Result shape | TIME_SERIES or SCALAR; TABLE is not supported. |
| Cannot carry | signal, filters, groupBy, aggregation, source — it never reads storage itself. |
| Can point at | A storage query in the same panel. Not itself, and not another ratio query. |
| Shape match | Numerator and denominator must have the same output.type as the ratio query. |
limit
Caps how many series a time series panel draws. The largest series are chosen first and only then spread over time, so hitting the cap never truncates the time range. 0 means the default of 20, and the maximum is 100. Asking for more is rejected with OUT_OF_RANGE rather than silently folded.
Whether a result was capped is reported in the execute response as meta.truncated and meta.series_limit.
Normalization
Once a document passes validation the server normalizes it before storing, so what is stored may differ from what you sent.
| Normalization | Detail |
|---|---|
| Defaults filled | Empty entries of settings |
| Empty arrays | null becomes [] for variables, sections, panels, filters, groupBy |
| Uppercased | Operator and aggregation function names |
Validation errors
Errors come back collected in one response. path is a JSON Pointer to the exact spot, and code is a stable identifier that survives wording changes.
{
"valid": false,
"schema_version": 1,
"summary": { "panel_count": 1, "variable_count": 0, "section_count": 0 },
"errors": [
{
"path": "/panels/0/queries/0/groupBy",
"code": "REQUIRED",
"message": "a TABLE result needs at least one group by"
},
{
"path": "/panels/0/layout",
"code": "OUT_OF_RANGE",
"message": "panel exceeds the 12 column grid (x=8, w=6)"
}
]
}| Code | Meaning |
|---|---|
UNSUPPORTED_SCHEMA_VERSION | Unreadable schemaVersion (or format_version on an import document) |
REQUIRED | A required entry is missing |
DUPLICATE | A name, ID, or column name repeats |
UNKNOWN_REFERENCE | Points at a panel that does not exist |
OUT_OF_RANGE | Outside the grid, count, or cap |
INVALID_VALUE | A value or combination that is not allowed |
INCOMPATIBLE_OUTPUT | Visualization and result shape do not match |
UNKNOWN_FIELD | Not in the dictionary, or not usable as an aggregation argument |
Codes raised during execution are not validation errors. The definition is stored, one query could not produce an answer, and the other panels still render.
| Execution code | Meaning |
|---|---|
QUERY_FAILED | That query failed to execute |
METRIC_NOT_INSTRUMENTED | The metric it points at exists nowhere in the retention window — not a fault, but a sign that instrumentation would fill it. message carries the metric name. |
Seeing real definitions
The built-in templates are the best examples. Create a dashboard from one and press export to get the finished definition document.