---
title: "Add an element to a page."
method: POST
path: "/v1/dashboards/{id}/pages/{pageId}/elements"
tags: ["dashboards"]
---

# Add an element to a page.

`POST /v1/dashboards/{id}/pages/{pageId}/elements`

## Request

- Base URL: `http://localhost:8080`
- URL: `http://localhost:8080/v1/dashboards/{id}/pages/{pageId}/elements`
- Auth: one of:
  - API key in cookie `dvt_session`
  - HTTP bearer

The granular create: adds one panel — validated against the element schema —
without round-tripping the whole spec. The element is placed into the page's
layout at (x, y, w, h) on every breakpoint, the page is versioned, and a new
dashboard revision is written. Refused on legacy (pre-decomposition)
dashboards (409 legacy-dashboard). Editor or above.

## Request body

- object
  - `slug` string: Optional; generated from the title when omitted.
  - `type` string, required
  - `title` string
  - `data` object
  - `spec` object
  - `overrides` object
  - `subtitle` string: Optional panel subtitle (DVT-3543).
  - `drill` object: Legacy drill-down block (DVT-3543).
  - `contextMenu` object: Right-click context menu binding (DVT-3543).
  - `brush` object: Brush-select interaction binding (DVT-3543).
  - `onClick` object: Single bare click-action binding — filter, drill, or openOverlay (DVT-3543).
  - `x` integer
  - `y` integer
  - `w` integer
  - `h` integer
  - `zIndex` integer
  - `layout` object: Optional per-breakpoint layout override; omitted breakpoints fall back to the flat x/y/w/h.
    - `lg` LayoutRect: A single breakpoint's grid geometry (DVT-883 per-breakpoint layout override). x and y default to 0 when omitted.
      - `x` integer
      - `y` integer
      - `w` integer, required
      - `h` integer, required
    - `md` LayoutRect: A single breakpoint's grid geometry (DVT-883 per-breakpoint layout override). x and y default to 0 when omitted.
      - `x` integer
      - `y` integer
      - `w` integer, required
      - `h` integer, required
    - `sm` LayoutRect: A single breakpoint's grid geometry (DVT-883 per-breakpoint layout override). x and y default to 0 when omitted.
      - `x` integer
      - `y` integer
      - `w` integer, required
      - `h` integer, required
    - `xs` LayoutRect: A single breakpoint's grid geometry (DVT-883 per-breakpoint layout override). x and y default to 0 when omitted.
      - `x` integer
      - `y` integer
      - `w` integer, required
      - `h` integer, required
  - `reason` string, required: Required provenance note (the "why") explaining this change; recorded on the element's initial revision (DVT-1658) and propagated into the dashboard revision's change summary so the Versions tab shows why the change was made (DVT-1792). May also be supplied via the `X-Dvt-Change-Reason` header. 1–280 characters.

## Response `201`

Element created (version 1), with themeContext.

- ElementEnvelope
  - `data` Element, required: A single dashboard element, fetched or patched without the rest of the spec. `themeContext`/`dvtRefs` are present on responses to let an agent interpret the element in isolation.
    - `elementId` string, uuid, required: Durable UUID primary key.
    - `slug` string, required: Readable, dashboard-scoped reference (the authored panel id), e.g. "panel-revenue".
    - `pageId` string, uuid, required
    - `dashboardId` string, uuid, required
    - `type` string, required
    - `title` string
    - `subtitle` string: Optional panel subtitle (POLISH-16).
    - `x` integer, required
    - `y` integer, required
    - `w` integer, required
    - `h` integer, required
    - `zIndex` integer
    - `data` object: Panel data binding (sourceId, query/metricRef, params, optional baked rows).
    - `spec` object: The element body (near-passthrough to ECharts); token references stay unresolved.
    - `overrides` object: Per-element token overrides.
    - `drill` object: Legacy drill-down block (POLISH-16).
    - `contextMenu` object: Right-click context menu binding (POLISH-16).
    - `brush` object: Brush-select interaction binding (POLISH-16).
    - `onClick` object: Single bare click-action binding — filter, drill, or openOverlay (POLISH-16).
    - `themeContext` object: Resolved values of every theme token this element references.
    - `resolvedTokens` object: The FULL resolved theme token map for this element — every key the tier stack (org baseline / preset / dashboard / page / element overrides) declares, i.e. the renderer's own ResolvedTokens (web/src/tokens.ts), not the referenced-token subset in `themeContext`. Present on GET-single-element responses only, and only when the caller opts in via `?include=themeTokens` (DVT-4298). A consumer that must reproduce what the browser PAINTED needs it: a table's header row is styled from `panel.background` and `text.secondary`, which no panel spec references, so `themeContext` cannot carry them and a formatted export built from it would fall back to default header colors.
    - `dvtRefs` string[]: Named $dvtRef registry keys the element uses (e.g. "formatter:pie-label").
    - `version` integer, required: Optimistic-concurrency version. Pass this back on PATCH.
    - `provenance` ElementProvenance: Bounded creation/last-edit summary for a single element (DVT-1665). `createdBy` is the version=1 revision's actor; `lastEdit` is the newest revision's actor. `revisionCount` is the total number of revisions (versions are sequential 1..N).
      - `createdBy` ElementProvenanceActor, required: Actor attribution for one point in an element's history (creation or most recent edit). `reason` and `version` are only meaningful on the `lastEdit` position — absent on `createdBy`.
        - `actorType` 'user' | 'agent' | 'system', required
        - `actorName` string: Resolved display name (display_name, else email). Absent for a system write with no actor.
        - `actorLabel` string: "via {key}" credential label (API-key name / MCP client id). Absent for first-party UI edits.
        - `createdAt` string, date-time, required
        - `reason` string: Optional user/agent-authored "why" for this revision. Only set on `lastEdit`.
        - `version` integer: The revision version this actor corresponds to. Only set on `lastEdit`.
      - `lastEdit` ElementProvenanceActor, required: Actor attribution for one point in an element's history (creation or most recent edit). `reason` and `version` are only meaningful on the `lastEdit` position — absent on `createdBy`.
        - `actorType` 'user' | 'agent' | 'system', required
        - `actorName` string: Resolved display name (display_name, else email). Absent for a system write with no actor.
        - `actorLabel` string: "via {key}" credential label (API-key name / MCP client id). Absent for first-party UI edits.
        - `createdAt` string, date-time, required
        - `reason` string: Optional user/agent-authored "why" for this revision. Only set on `lastEdit`.
        - `version` integer: The revision version this actor corresponds to. Only set on `lastEdit`.
      - `revisionCount` integer, required
  - `meta` Meta, required: Response metadata.
    - `requestId` string, required: Correlates with the X-Request-Id header.
    - `total` integer: Item count, present on collection responses.
    - `nextCursor` string, nullable: Opaque keyset-pagination cursor, present on paginated responses (e.g. GET /v1/dashboards) when more rows exist beyond this page. Pass it back as the `cursor` query param to fetch the next page; absent or null on the last page.
    - `roleResolveDegraded` boolean: Snowflake mode only (DVT-2260), on GET /v1/dashboards: present and true when the caller's live Snowflake role resolution failed or was unavailable — role-shared dashboards may be missing from this page. Omitted (not merely false) whenever resolution succeeded or the role-share arm does not apply (off snowflake mode, an org admin/owner, or off the ingress listener).

## Other responses

- `400`: Malformed request, or a `python` panel this write seam refused (ADR-0084 D2/I7). Distinguished by the Problem `type`; every python refusal also echoes its slug in the `X-Dvt-Error-Code` response header: - `https://docs.dvt.dev/errors/bad-request` (the default) — a malformed or otherwise invalid request body. - `https://docs.dvt.dev/errors/python-source-unknown` — a python panel's `data.sourceId` names no live connection in this organization. A connection in ANOTHER organization reads identically, by design. List the ones you can use with `GET /v1/connections`. - `https://docs.dvt.dev/errors/python-source-not-snowflake` — the panel's source resolves but is not a Snowflake connection. A python panel executes as an anonymous procedure inside Snowflake and has nowhere else to run; express the panel as SQL against the current source instead. - `https://docs.dvt.dev/errors/python-source-not-pushdown` — the source is Snowflake but a `writeback`-kind connection (a Sheets flush target, not a queryable source). Point `data.sourceId` at a pushdown connection. Only a write that ADDS or CHANGES a python panel is checked: a title-, layout-, or geometry-only edit of one already stored is an ordinary dashboard write, and deleting one is never an authoring act.
- `401`: Missing, invalid, or expired session.
- `403`: The caller's role does not permit this action, or a `python` panel this write seam refused on identity grounds (ADR-0084 D2/I7). Distinguished by the Problem `type`: - `https://docs.dvt.dev/errors/forbidden` — the caller's role does not permit this action; for a python panel, it lacks the `python:author` capability (the editor floor), or an API key's scopes exclude it. - `https://docs.dvt.dev/errors/not-in-edition` — python panels are not available on this deployment at all (`editionCapabilities.pythonPanels` is false). **The 1.0.20 Snowflake native app ships `PYTHON_PANELS_ENABLED="true"` as of DVT-4453** (risk accepted 2026-09-19; ADR-0084 H5 remains a hard gate for the next cut, not a closed item), so this response is no longer expected there by default. On the native app the literal is baked into the package at build time, so it would fire again only after a provider-side package patch from dvt turns it back off — a consumer install cannot flip it itself; the in-account lever is the `python:author` editor floor via the app roles. On Gallery and Enterprise it fires whenever the operator's own env literal is off. Same shape as the linked-Sheet cell gate's refusal. Use a SQL-backed panel, or ask your dvt operator. - `https://docs.dvt.dev/errors/python-author-requires-admin` — the panel's source is a SHARED-credential connection (any `service_*` auth method, the internal `platform_managed` one, or `cloud_iam`, which is the server's own ambient workload identity), so the authored code would run under a credential no viewer holds: authoring it requires the admin/owner rank, over and above `python:author`. Also echoed in `X-Dvt-Error-Code`. Only the two caller's-rights methods keep the editor floor — `oauth_passthrough` and `spcs_token`, each of which opens the warehouse session as the acting user.
- `404`: Resource does not exist.
- `409`: Slug already in use (slug-taken), legacy dashboard, or an htmlSlots page (which positions panels via `<dvt-slot ref>` in the layout HTML, not the coordinate grid — edit the layout HTML instead).
- `422`: The spec failed JSON Schema validation. `fields` lists each violation.

---

[API](https://skmtc.dev/dvt/apis/dvt-rest-api.md) · [All operations](https://skmtc.dev/dvt/apis/dvt-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dvt/apis/dvt-rest-api/revisions/00016f504165?raw)
