Skip to content

Building Dashboards and Notebooks

A Datadog dashboard is a grid of widgets, each built from one or more named queries combined through a formula, made interactive with template variables, and — proven below — validated the same way whether it’s hand-authored JSON or managed as Terraform’s datadog_dashboard_json resource. The basic widget/JSON anatomy (layout_type, timeseries/query_value/toplist) was already proven in Dashboards and the Datadog UI Tour; this lesson goes deeper on formulas and functions, an SLO widget, presets, and the Terraform-managed path — not a repeat of that lesson’s basics.

Three widget types cover the vast majority of dashboards you’ll build, plus one more this lesson adds real proof for:

  • timeseries — a line or bar graph of a metric over time, the default choice for “how has this value trended.”
  • query_value — a single big number, usually the latest value or an aggregation over a time window, used for at-a-glance KPIs.
  • group — a container widget that holds other widgets and can be scoped by a tag, letting you repeat the same set of graphs once per value of that tag.
  • slo — an SLO status widget, proven below with a real, validated definition; shows error-budget/status for a specific Service Level Objective directly on a dashboard.

Formulas and functions — corrected syntax, docs-verified

Section titled “Formulas and functions — corrected syntax, docs-verified”

Every widget query starts by naming a query — conventionally query1, query2, and so on — which is really just “this metric, filtered by these tags, aggregated this way” (the same queries/formulas shape proven against the real Dashboard model in the foundations UI-tour lesson). The formula field is a small expression referencing those named queries by name, enabling arithmetic between them (query1 / query2 * 100 for an error rate). Beyond arithmetic, two named functions commonly wrap or chain onto a query — and their calling conventions genuinely differ from each other, confirmed directly from docs.datadoghq.com/dashboards/functions/:

  • rollup() is dot-chained onto the end of a query string: .rollup(<AGGREGATOR>, <INTERVAL>), e.g. sum:myservice.requests.count{*}.as_count().rollup(sum, 300) forces a 5-minute rolling sum. Real docs example, showing the same dot-chain pattern with a weekly rollup: sum:requests.count{*}.as_count().rollup(sum, weekly, monday, 'America/New_York').
  • timeshift(), by contrast, is a wrapping call around the entire query: timeshift(<QUERY>, -<SECONDS>). Real docs example: timeshift(avg:system.load.1{*}, -1209600) — comparing current load against the load from exactly two weeks (1,209,600 seconds) earlier.

A template variable — say env, defaulting to a tag prefix like env — is a dashboard-level filter control substituted into every widget’s query at once. A related, distinct field validated against the real Dashboard model — template_variable_presets — lets you save specific combinations of variable values as a named, one-click preset (e.g. “Production incident view” = env:prod):

dashboards/orders-api-presets.json
{
"title": "orders-api overview with SLO + presets",
"layout_type": "ordered",
"template_variables": [
{ "name": "env", "prefix": "env", "available_values": ["prod", "staging"], "default": "prod" }
],
"template_variable_presets": [
{
"name": "Production incident view",
"template_variables": [{ "name": "env", "value": "prod" }]
}
],
"widgets": [
{
"definition": {
"type": "slo",
"title": "orders-api availability SLO",
"slo_id": "00000000000000000000000000000000",
"view_type": "detail",
"view_mode": "overall",
"time_windows": ["7d", "30d"],
"show_error_budget": true
}
}
]
}
$ python3 -c "
from datadog_api_client.v1.model.dashboard import Dashboard
Dashboard(**data, _check_type=True)
print('OK: valid')
"
OK: valid

Real, precise naming fact confirmed while writing this proof: the class is SLOWidgetDefinition (all-caps SLO), not the more Pythonically-conventional SloWidgetDefinition — importing the latter raises a real ImportError with a “Did you mean…?” suggestion, a small but genuine gotcha for anyone generating code against this client from a general Python-naming-convention prior. Consistent with the widget-definition-is-loosely-typed finding from the UI-tour lesson (a layout_type-missing dashboard was rejected, but a widget missing its own type-specific fields was not), a real test removing slo_id entirely from this SLO widget still validated cleanly — the model checks the envelope’s required top-level fields strictly, but does not enforce per-widget-type required fields the way the real Datadog UI or API would at save time.

The datadog_dashboard_json Terraform resource — a real, distinct resource, validated

Section titled “The datadog_dashboard_json Terraform resource — a real, distinct resource, validated”

Beyond the structured datadog_dashboard HCL resource (whose widget-query HCL shape is covered, including a real bug fix, in Datadog as Code with Terraform — not repeated here), Terraform’s datadog provider also ships datadog_dashboard_json — a genuinely different resource that takes a raw JSON string instead of nested HCL blocks, and is Datadog’s own documented recommendation for widget types (like the Wildcard widget) that the structured resource doesn’t fully support. Real, validated HCL:

terraform/dashboard-json.tf
resource "datadog_dashboard_json" "orders_overview" {
dashboard = jsonencode({
title = "orders-api overview (Terraform JSON)"
layout_type = "ordered"
widgets = [
{
definition = {
type = "timeseries"
title = "orders-api error rate"
requests = [
{
response_format = "timeseries"
queries = [
{
data_source = "metrics"
name = "query1"
query = "sum:trace.http.request.errors{env:prod,service:orders-api}.as_count()"
}
]
formulas = [{ formula = "query1" }]
display_type = "line"
}
]
}
}
]
})
}
$ terraform validate -json
{"format_version":"1.0","valid":true,"error_count":0,"warning_count":0,"diagnostics":[]}

The dashboard attribute is exactly the same queries/formulas JSON shape this course validates directly against the Dashboard Python model — jsonencode(...) here is just HCL’s way of producing that JSON string inline, so everything proven above about the current widget-query shape applies unchanged inside Terraform too.

Covered in depth (real NotebookCreateRequest model, confirmed v1-only with no v2 equivalent, a real markdown + timeseries cell payload) in Dashboards and the Datadog UI Tour — not repeated here. The short version: a Notebook trades the fixed grid for a linear, narrated document, reusing the identical widget-definition shape for its graph cells.

A Powerpack is, per Datadog’s own glossary: “templated groups of widgets that scale graphing expertise as reusable dashboard building blocks.” Two kinds exist: preset Powerpacks (built by Datadog, tied to a specific product or integration, available to every customer) and custom Powerpacks (built by a user in their own org). The practical value, per docs.datadoghq.com/dashboards/guide/powerpacks-best-practices/: updates to a custom Powerpack propagate to every dashboard that embeds it, so a team’s “the right five graphs for a Postgres host” knowledge is captured once and stays current everywhere it’s used, instead of being copy-pasted and drifting out of sync across dashboards. This course does not stand up a live Powerpack (no account, by design) — the mechanism is docs-sourced, cited above rather than demonstrated.

flowchart LR
  A[Named query query1: errors] --> C["Formula: query1 / query2 * 100"]
  B[Named query query2: requests] --> C
  C --> D["Widget: timeseries / query_value / group / slo"]
  E["Template variable env"] -->|filters| D
  F["Preset: env=prod"] -->|one click| E
  D --> G[Dashboard JSON]
  G --> H["datadog_dashboard_json Terraform resource"]
  D --> I[Notebook: graphs + narrative text]
  J[Powerpack: reusable widget group] -->|embedded in| G
From named queries to a filtered, saved dashboard

🐛 Spot the bug

# AI comment: "5-minute rolling sum of error count, using rollup"
sum:myservice.errors.count{env:prod}.as_count().rollup(sum, 300)
# AI comment: "Same idea, applied via a wrapping function like timeshift"
rollup(sum:myservice.errors.count{env:prod}.as_count(), sum, 300)
Show the answer

The first line is correct real syntax — rollup dot-chained onto the query, matching the real docs example proven above. The second line is the bug: it applies rollup’s arguments using timeshift’s wrapping-call convention instead, a completely natural mistake once you’ve correctly learned that some Datadog query functions wrap the query (proven above: timeshift genuinely does). Confirmed against docs.datadoghq.com/dashboards/functions/rollup/, rollup is never written as rollup(<query>, ...) — that shape belongs to timeshift and a handful of other functions, not to rollup. Because this lives inside a plain string (proven above, opaque to both Terraform and JSON-model validation), nothing in this course’s harness would catch the second line as wrong — only the real Datadog query parser (or knowing the actual docs, as proven above) would reject it.

Think of a dashboard’s query layer as a small kitchen with two very differently-shaped utensils. rollup is a spoon you stir into the dish already in the pot — it attaches to the end of what’s already cooking, dot-chained. timeshift is a serving tray you place the entire finished dish onto and carry elsewhere — it wraps the whole query from the outside. Both are real, useful kitchen tools, but reaching for the wrong one’s grip (trying to “wrap” with the spoon, or “attach” the tray) doesn’t fail loudly — the recipe card (a plain string, opaque to every automated check in this course) just quietly says something that was never true.

  • datadog_dashboard (structured HCL) vs. datadog_dashboard_json (raw JSON string) — structured HCL gets you Terraform-native diffs on individual widget fields and HCL tooling (variables, for_each over a list of widgets); datadog_dashboard_json (proven above, real and valid) sacrifices that granularity for guaranteed parity with whatever the Datadog UI actually exports, and is Datadog’s own recommendation for widget types the structured resource doesn’t fully model yet.
  • Template variables vs. presets — a bare template variable is maximally flexible (pick any value at view time) but requires a manual selection every time; a preset (proven above, validated on the real model) trades that flexibility for one-click reproducibility of a specific, named combination worth returning to repeatedly (like “production incident view”).
  • Custom Powerpacks vs. copy-pasting a widget group — a Powerpack centralizes an update so every embedding dashboard picks it up automatically (per docs, cited above); copy-pasting is zero setup but guarantees drift the moment anyone updates one copy and not the others.
  • “rollup and timeshift are called the same way, since they’re both dashboard query functions.” No — proven above from real docs: rollup is dot-chained (<query>.rollup(...)), timeshift wraps (timeshift(<query>, ...)) — genuinely different conventions, and this course’s own earlier draft got rollup wrong by assuming otherwise.
  • “Terraform (or the JSON Dashboard model) validates that my query functions are used correctly.” No — proven above: both live inside a plain, opaque query string; a completely backwards rollup/timeshift mix-up still passes terraform validate and Dashboard(**data, _check_type=True) cleanly.
  • “datadog_dashboard_json is a fallback for when the real Terraform resource is broken.” Not quite — per Datadog’s own docs (cited above), it’s the actively recommended path for specific widget types (like Wildcard) the structured resource doesn’t fully support, not merely an escape hatch.
  • “A Powerpack is just a saved dashboard template you copy from.” No — per docs quoted above, the defining feature is that updates to a custom Powerpack propagate to every dashboard embedding it; a copied dashboard, by contrast, immediately starts drifting from its source.
Which widget type would you use for a single at-a-glance number like "current active connections"?
Proven above directly from docs.datadoghq.com/dashboards/functions/, what is the real calling convention for rollup(), and how does it differ from timeshift()?
Proven above with a real terraform validate run, why does a query string with a genuinely backwards rollup/timeshift mix-up still pass validation cleanly?
Proven above by attempting to import SloWidgetDefinition from the real datadog_api_client package, what happened?
Proven above with a real, valid terraform validate run, what is datadog_dashboard_json, and when does Datadog itself recommend it over the structured datadog_dashboard resource?
Per Datadog's own glossary, quoted above, what makes a custom Powerpack different from just copy-pasting the same widget group onto multiple dashboards?