Skip to content

Dashboards and the Datadog UI Tour

Every view in Datadog — Infrastructure, Metrics Explorer, Logs Explorer, APM, Dashboards — is filtered and scoped with the same tag syntax, so once you know env/service/version from the previous lesson, you already know how to navigate all of them. The UI itself is best learned by using it (this lesson is docs-oriented, not proof-heavy like the others), but the dashboard/notebook JSON underneath it is real, current API shape, proven below against the actual datadog_api_client models.

Five views cover almost everything you’ll do day to day:

  • Infrastructure List / Host Map — every host or container Datadog knows about, as a sortable list or a colored grid (the Host Map), colorable by any metric (CPU, memory) and filterable by tag.
  • Metrics Explorer — build ad-hoc graphs over any metric, sliced by tag, without committing to a saved dashboard — this is where you go to answer “what does this number actually look like right now.”
  • Logs Explorer — search and filter indexed log events, build facets out of log attributes, and pivot straight from a log line to its trace if trace injection is set up.
  • APM Traces / Services — the Services list shows every service tag value Datadog has seen traces for, with latency/error/throughput at a glance; drilling into one service shows its traces and its place in the service map.
  • Dashboards — curated, saved collections of widgets, built once and reused by the whole team.
flowchart TD
    Filter["Tag filter: env:prod service:orders-api"] --> Infra["Infrastructure List / Host Map"]
    Filter --> Metrics["Metrics Explorer"]
    Filter --> Logs["Logs Explorer"]
    Filter --> APM["APM Traces / Services"]
    Filter --> Dash["Dashboards"]
One tag filter, five views

Datadog Dashboards come in two layout types, and picking the right one up front saves a lot of rework later:

  • Ordered — widgets stack automatically in the sequence you add them, top to bottom. You don’t manage pixel positions; you manage a list. This is the right default for most dashboards — fast to build, easy to reorder, and it reflows sanely on any screen size.
  • Free — you place each widget manually with explicit x, y, width, and height coordinates, giving you a fixed, deliberate layout — useful for a status-board-style dashboard meant to be viewed on a big screen where visual grouping matters more than convenience of editing.
// A widget on a free dashboard carries explicit layout coordinates
{
"definition": { "type": "timeseries", "title": "orders-api error rate" },
"layout": { "x": 0, "y": 0, "width": 6, "height": 3 }
}
// The same widget on an ordered dashboard has no layout block —
// its position is just its position in the widgets array
{
"definition": { "type": "timeseries", "title": "orders-api error rate" }
}

If you’re not sure which one you need, start with ordered — you can always widen a widget or split it into two, and you never have to hand-tune coordinates just to add one more graph.

Dashboard JSON anatomy, validated against the real model

Section titled “Dashboard JSON anatomy, validated against the real model”

A dashboard is a JSON document with a flat set of top-level fields plus a widgets array; each widget has a definition (what kind of widget, what it queries) and, on a free dashboard only, a layout. Real, current-shape example — three widget types, template variables, layout_type — validated with datadog_api_client.v1.model.dashboard.Dashboard(**data, _check_type=True):

dashboards/orders-api-overview.json
{
"title": "orders-api overview",
"description": "Error rate, request volume, and top endpoints for orders-api",
"layout_type": "ordered",
"template_variables": [
{ "name": "env", "prefix": "env", "available_values": ["prod", "staging"], "default": "prod" },
{ "name": "service", "prefix": "service", "available_values": ["orders-api"], "default": "orders-api" }
],
"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,$service}.as_count()"
}
],
"formulas": [{ "formula": "query1" }],
"display_type": "line"
}
]
}
},
{
"definition": {
"type": "query_value",
"title": "orders-api request volume (5m)",
"requests": [
{
"response_format": "scalar",
"queries": [
{
"data_source": "metrics",
"name": "query1",
"query": "sum:trace.http.request.hits{$env,$service}.as_count()"
}
],
"formulas": [{ "formula": "query1" }]
}
]
}
},
{
"definition": {
"type": "toplist",
"title": "Top endpoints by error count",
"requests": [
{
"response_format": "scalar",
"queries": [
{
"data_source": "metrics",
"name": "query1",
"query": "sum:trace.http.request.errors{$env,$service} by {resource_name}.as_count()"
}
],
"formulas": [{ "formula": "query1" }]
}
]
}
}
]
}
$ python3 -c "
import json
from datadog_api_client.v1.model.dashboard import Dashboard
data = json.load(open('orders-api-overview.json'))
Dashboard(**data, _check_type=True)
print('OK: valid')
"
OK: valid

Two things worth calling out about the query shape, confirmed by introspecting the real, installed datadog_api_client model rather than guessing: a widget request’s queries array holds a data_source/name/query object per data source ("data_source": "metrics" for a metric query), and formulas is a separate array referencing those query names ({"formula": "query1"}) — this is the current formulas-and-functions shape, the same shape the review flagged as wrong in this course’s Terraform datadog_dashboard resource (that fix lives in datadog-as-code-with-terraform, not here, but the underlying API shape is identical whether you write it as Terraform HCL or hand-authored JSON). The model also still accepts a legacy bare "q": "sum:trace...{...}" string field on a request instead of queries/formulas — both are real, valid fields on the same model — but queries/formulas is the current, recommended shape and the one this lesson uses throughout.

A real, caught error: a dashboard missing layout_type

Section titled “A real, caught error: a dashboard missing layout_type”

layout_type is not optional — it’s a required constructor argument on the real model, and a dashboard JSON pasted from an example that only shows the widgets array (a very plausible AI-generated-code omission) fails immediately and loudly:

// @expect-error dashboards/orders-api-overview-broken.json -- missing required layout_type
{
"title": "orders-api overview",
"widgets": []
}
$ python3 -c "
from datadog_api_client.v1.model.dashboard import Dashboard
Dashboard(title='orders-api overview', widgets=[], _check_type=True)
"
TypeError: Dashboard.__init__() missing 1 required positional argument: 'layout_type'

Notebooks are a lighter-weight alternative to a Dashboard for a different job: telling a story, not maintaining a live status board. A Notebook mixes graphs, log queries, and free-text markdown in one scrolling document — the natural place to write up an incident timeline (“here’s the latency graph during the spike, here’s the deploy marker, here’s what we concluded”), a postmortem, or a one-off investigation you want to hand to a teammate. Unlike a Dashboard, a Notebook is meant to be read top to bottom once, not glanced at repeatedly.

Notebook JSON, and a real model-naming gotcha

Section titled “Notebook JSON, and a real model-naming gotcha”

A Notebook’s create payload is a nested envelope: {"data": {"type": "notebooks", "attributes": {"name", "time", "cells": [...]}}}. Real fact worth stating explicitly, confirmed by searching the installed datadog_api_client package rather than assuming: Notebooks only exist in the v1 module — there is no datadog_api_client.v2.model.notebook* at all in this client, unlike Dashboard/Monitor/SLO which this course validates the same way. If you’re writing code against Notebooks, v1 is not a legacy fallback here, it’s the only option. The top-level model is NotebookCreateRequest, validated the same way as the Dashboard example above:

// notebooks/orders-api-incident.json -- not a harness-collected directory
// (dashboards/monitors/slos/service-catalog/logs-pipelines only); validated
// by hand against datadog_api_client.v1.model.notebook_create_request below.
{
"data": {
"type": "notebooks",
"attributes": {
"name": "orders-api incident 2026-09-29",
"time": { "live_span": "4h" },
"cells": [
{
"type": "notebook_cells",
"attributes": {
"definition": {
"type": "markdown",
"text": "## orders-api latency spike\n\nTimeline of the incident and what we concluded."
}
}
},
{
"type": "notebook_cells",
"attributes": {
"definition": {
"type": "timeseries",
"title": "orders-api error rate",
"requests": [
{
"response_format": "timeseries",
"queries": [
{
"data_source": "metrics",
"name": "query1",
"query": "sum:trace.http.request.errors{service:orders-api}.as_count()"
}
],
"formulas": [{ "formula": "query1" }],
"display_type": "line"
}
]
},
"time": { "live_span": "4h" }
}
}
]
}
}
}
$ python3 -c "
import json
from datadog_api_client.v1.model.notebook_create_request import NotebookCreateRequest
data = json.load(open('orders-api-incident.json'))
NotebookCreateRequest(**data, _check_type=True)
print('OK: valid')
"
OK: valid

A Notebook cell reuses the exact same widget definition shape as a Dashboard widget (the timeseries cell above has an identical requests/queries/formulas structure to the Dashboard example) — a markdown cell is the one Notebook-specific addition, since telling a story requires prose between the graphs.

The reason all of this hangs together: Infrastructure, Metrics Explorer, Logs Explorer, APM, and Dashboards all accept the exact same tag filter syntax — env:prod, service:orders-api, version:1.4.2, combined with AND/OR as needed. Set up unified service tagging correctly (previous lesson) and the same three-value filter narrows every one of these views down to “this deployable unit, this environment, this release” — which is the whole reason this course spent an entire lesson on tagging before ever touching dashboards.

🐛 Spot the bug

// AI comment: "Dashboard JSON for the orders-api overview"
{
"title": "orders-api overview",
"description": "Error rate and request volume for orders-api",
"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{service:orders-api}.as_count()" }
],
"formulas": [{ "formula": "query1" }]
}
]
}
}
]
}
Show the answer

The widget itself is real, current shape — queries/formulas, correct field names, valid metric query syntax. The bug, proven above with the real Dashboard model: this JSON is missing layout_type, and it is not an optional field with a sensible default — the real Python client raises TypeError: Dashboard.__init__() missing 1 required positional argument: 'layout_type' immediately. Plenty of dashboard JSON examples floating around (including ones AI training data was built from) show only the widgets array in isolation as “the interesting part,” which is exactly how this omission slips through — the fix is one line, "layout_type": "ordered" (or "free", with matching layout blocks on every widget), but without it the payload is rejected before Datadog even looks at the widgets.

Think of a Dashboard’s JSON as a seating chart plus a script for each seat. layout_type decides whether the venue auto-arranges seats in rows (ordered) or you hand-place every chair with coordinates (free) — proven above, the venue refuses to open at all without knowing which. Each widget’s definition is that seat’s script: what to say (queries, data_source: metrics, the actual query string) and how to say it (formulas, combining one or more named queries into what’s actually displayed). A Notebook cell is the same script format, just interspersed with plain narration (markdown cells) instead of every seat being a graph.

  • Ordered vs. free layout — ordered is near-zero-maintenance (add a widget, it just appears at the end) but you can’t create deliberate visual groupings beyond the linear order; free gives full pixel control for a status-board layout, at the cost of every future edit needing coordinate math.
  • Bare q string vs. queries/formulas — proven above, both are real, valid fields on the same widget-request model; q is shorter to hand-write for a single simple query, queries/formulas is what supports combining multiple queries with math (the entire point of “formulas and functions”) and is the shape this course, the API docs, and the UI itself all generate today.
  • Dashboard vs. Notebook for the same underlying widget — proven above, a Notebook’s timeseries cell and a Dashboard’s timeseries widget share the identical requests/queries/formulas shape, so the actual query-authoring skill transfers completely; the difference is purely structural (a live, reusable grid vs. a linear, narrated document), not a difference in query capability.
  • “A dashboard JSON just needs a title and a widgets array.” No — proven above: layout_type is a required constructor argument on the real Dashboard model, and omitting it is a real, immediate TypeError, not a silently-defaulted value.
  • “The current widget query format is a plain query string like the old Terraform datadog_dashboard bug used.” No — proven above by inspecting the real model: the current, recommended shape is queries (an array of named, data-sourced query definitions) plus formulas (referencing those names); a bare q string still validates on the model but is the older shape, not the one this lesson (or the fixed Terraform lesson) uses.
  • “Notebooks have their own separate v2 API model, like Dashboards and Monitors do.” No — confirmed by searching the installed client: Notebooks exist only in datadog_api_client.v1, with no v2 equivalent at all.
  • “A Notebook’s graph cells use a completely different query format from a Dashboard’s widgets.” No — proven above: a Notebook timeseries cell’s definition is structurally identical to a Dashboard timeseries widget’s definition, right down to the same queries/formulas fields.
Which view would you open to see every service Datadog has traces for, with latency/error/throughput at a glance?
What is the key difference between an ordered dashboard and a free dashboard?
Proven above by constructing the real Dashboard model with only title/widgets, what happens when layout_type is omitted?
Proven above by inspecting the real, installed datadog_api_client model, what is the current, recommended shape for a widget's metric query, as opposed to the older shape?
Proven above by searching the installed datadog_api_client package, which statement about the Notebooks API model is correct?
Why can you use the same env:prod AND service:orders-api filter in the Logs Explorer and in APM?