# Dashboard YAML

> Source: https://docs.clonepartner.com/reference/dashboards/

<!-- sources: src/dashboard-parser.ts, references/envoy/dashboards.md -->

# Dashboard YAML

Dashboards are YAML-defined widget pages bound to Jobs. Definitions are authored on dashboard templates attached to a Job Template and snapshotted onto each Job's `dashboards` rows at job creation, with `${VAR}` substitution from the job's arguments. `parseDashboardYaml` in `src/dashboard-parser.ts` enforces this schema; the document may start with a top-level `dashboard:` key or be a bare dashboard object.

This page covers the document structure. Widget configuration lives in the [Widgets overview](/reference/widgets/) and its group pages; the authoring guide is [Authoring dashboards](/building/authoring-dashboards/).

## Top-level keys

| Key | Type | Required | Description |
|---|---|---|---|
| `title` | string | No | Display title rendered in the page header. |
| `description` | string | No | Display description. |
| `navbar_ctas` | array | No | Header action group for the public share surface (see below). |
| `refresh_interval_seconds` | number | No | Dashboard-level polling interval. |
| `scope` | object | No | Named read-only filter profiles: profile name → connector name → filter fields (literal or JSONata string). |
| `default_scope` | string | No | Profile applied when a widget omits `scope`. Must name a defined profile. |
| `allow_public_input` | boolean | No | Allow input widgets to be edited through the public share link (also gated by the server kill switch). |
| `shared_queries` | object | No | Named read-only connector calls resolved once per refresh and shared by widgets. |
| `pages` | array | No | Multi-page layout. Mutually exclusive with top-level `widgets`. |
| `widgets` | array | No | Legacy single-page widget list, normalized into one page with `id: main`. |

## `pages`

Prefer `pages[]` for multi-section dashboards. Each page:

| Key | Type | Required | Description |
|---|---|---|---|
| `id` | string | Yes | Stable page identity; unique across the document. |
| `title` | string | Yes | Navigation label. |
| `widgets` | array | Yes | Ordered widget list. |
| `public` | boolean | No | Set `false` to hide the page (and its widgets) from public share links. Default: visible. |

```yaml
dashboard:
  title: Ops console
  refresh_interval_seconds: 60
  pages:
    - id: overview
      title: Overview
      widgets:
        - id: intro
          type: heading
          title: Status
    - id: data
      title: Data
      public: false
      widgets:
        - id: tickets
          type: explorer
          data:
            connector: mongo
            resource: tickets
```

Widget `id`s must be unique across the whole document, not just within a page. A document cannot declare both `pages` and top-level `widgets`.

## Widget types

Fourteen widget types are registered. Their per-type configuration is documented on three group pages:

| Type | Group | Purpose |
|---|---|---|
| `heading` | [Display widgets](/reference/widgets/display/) | Section labels and instructional callouts. |
| `stat` | [Display widgets](/reference/widgets/display/) | Single numeric result. |
| `progress` | [Display widgets](/reference/widgets/display/) | Current/total ratio bar. |
| `steps` | [Display widgets](/reference/widgets/display/) | Horizontal process stepper. |
| `people` | [Display widgets](/reference/widgets/display/) | Contact cards. |
| `bar_chart` | [Chart and table widgets](/reference/widgets/charts-and-table/) | Bar chart over an array of records. |
| `line_chart` | [Chart and table widgets](/reference/widgets/charts-and-table/) | Line chart over an array of records. |
| `pie_chart` | [Chart and table widgets](/reference/widgets/charts-and-table/) | Pie chart over an array of records. |
| `table` | [Chart and table widgets](/reference/widgets/charts-and-table/) | Bounded result set with declared columns. |
| `form` | [Input and action widgets](/reference/widgets/inputs-and-actions/) | Typed fields writing a `job_config` object. |
| `field_mapping` | [Input and action widgets](/reference/widgets/inputs-and-actions/) | Source-to-target mapping editor writing a `job_config` array. |
| `action` | [Input and action widgets](/reference/widgets/inputs-and-actions/) | Declarative server-executed button. |
| `task_progress` | [Input and action widgets](/reference/widgets/inputs-and-actions/) | Latest run status for one job task key. |
| `explorer` | [Input and action widgets](/reference/widgets/inputs-and-actions/) | Curated pageable data browser. |

Keys shared by every widget (`id`, `title`, `width`, `scope`, `refresh_interval_seconds`, and data binding) are documented once on the [Widgets overview](/reference/widgets/).

## `scope` and `default_scope`

Scope profiles inject read-only filters into widget connector calls, so one dashboard definition can serve different data slices:

```yaml
dashboard:
  default_scope: source
  scope:
    source: |
      {
        "mongo": {
          "job_id": arguments.job_id
        }
      }
```

- A widget without a `scope` key uses `default_scope`.
- `scope: <name>` on a widget selects a specific profile; `scope: null` opts the widget out of injection.
- `default_scope` must name a defined profile; otherwise parsing fails with `default_scope "<name>" is not defined in scope`.

Scope is merged into read-only connector calls (Mongo `filter`, aggregate `$match` prefix, Truto top-level args). SQLite `select` does not auto-inject scope because raw SQL is opaque.

## `shared_queries`

Named read-only connector calls resolved once per refresh and cached. Widgets reference an entry with `shared_query: <name>` and derive their value with a JSONata expression; the shared result is bound as `data` in the widget's expression context.

```yaml
dashboard:
  shared_queries:
    tickets_facet:
      connector: mongo
      method: aggregate
      args:
        collection: zendesk_tickets
        pipeline:
          - "$facet":
              total: [{ "$count": "count" }]
              migrated:
                - { "$match": { "status": "migrated" } }
                - { "$count": "count" }
  widgets:
    - id: tickets_total
      type: stat
      shared_query: tickets_facet
      value_expr: "$exists(data[0].total[0]) ? data[0].total[0].count : 0"
```

## `navbar_ctas`

Optional action group rendered in the public dashboard header:

| Key | Type | Required | Description |
|---|---|---|---|
| `label` | string | Yes | Button or link text. |
| `href` | string | Yes | Must be an `http(s)`, `mailto:`, `tel:`, or relative URL. |
| `variant` | string | No | `button` (default) or `link`. |
| `icon` | string | No | Icon name. |
| `text` | string | No | Secondary display text. |

```yaml
navbar_ctas:
  - label: Support
    variant: button
    icon: mail
    href: mailto:support@example.com
    text: support@example.com
```

## Attachment and sharing model

- Dashboard templates belong to a Job Template; Dashboards belong to a Job. Creating a Job snapshots each active dashboard template onto a `dashboards` row.
- A per-dashboard share token (`POST /api/dashboards/:id/share`) exposes a public read-only view. The public payload is sanitized: connector call configs, `shared_queries`, `scope`, and action dispatch internals are stripped; pages with `public: false` and actions without `allow_public: true` are omitted.
- `allow_public_input: true` allows input widgets to be edited through the share link, and only when the server-side kill switch `ENVOY_PUBLIC_DASHBOARD_INPUT_ENABLED` is also enabled.

## Validation errors

| Message | Cause |
|---|---|
| `dashboard.pages must be a non-empty array` | `pages` is present but empty or not an array. |
| `dashboard cannot have both pages and top-level widgets` | Choose one layout style. |
| `dashboard.pages[<i>].id is required` / `.title is required` / `.widgets must be an array` | A page is missing a required key. |
| `Duplicate page id: <id>` | Two pages share an `id`. |
| `Duplicate widget id: <id>` | Two widgets share an `id` anywhere in the document. |
| `dashboard.widgets must be an array` | Neither `pages` nor a `widgets` array is present. |
| `widgets[<i>].type must be one of: ...` | Unknown widget type. |
| `widgets[<i>].id is required` | A widget is missing `id`. |
| `dashboard.navbar_ctas[<i>].href must be an http(s), mailto, tel, or relative URL` | Unsafe CTA URL. |
| `default_scope "<name>" is not defined in scope` | `default_scope` names a missing profile. |

## Related

- [Widgets overview](/reference/widgets/) — the 14 widget types and shared widget keys
- [Authoring dashboards](/building/authoring-dashboards/) — design, testing, and public-share guidance
- [job_config schema](/reference/job-config/) — the values input widgets read and write
