---
title: "Export a dashboard (or one page) as one workbook, a sheet per data panel."
method: POST
path: "/v1/dashboards/{id}/export"
tags: ["dashboards"]
---

# Export a dashboard (or one page) as one workbook, a sheet per data panel.

`POST /v1/dashboards/{id}/export`

## Request

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

Exports every data-bound panel of the dashboard, or of one page (`pageId`), as ONE
XLSX workbook with one sheet per panel (DVT-4175), or as a zip of one CSV per panel
(`format: csv`). Panel queries are read from the STORED spec; the client sends ids and
filter params, NEVER SQL (unlike `POST /v1/data/export`). Each distinct data source is
authorized and resolved exactly as `POST /v1/data/query` does it (tenant check, role
mapping, per-user OAuth grants, uploaded-file URLs, decrypt audit), and every panel
query consumes the same per-org query rate limits. Results are never cached, the request is
recorded as a `data.export` audit entry (not written when every panel failed), and
it requires `data:query` AND `data:export` plus `dashboard:read` on this dashboard.
Render-session tokens cannot reach it.

Scope: with `pageId` omitted the export covers the root panels plus every non-hidden
page; with `pageId` set, that page. Sheets follow page order then panel order; a
sheet is named after the panel title (falling back to its id), made Excel-legal,
de-duplicated with " (2)", " (3)" and cut to 31 characters. Panels that are not
data-bound (text, filter, action-button, container ...) are skipped silently. Each
sheet gets the same formatting a single-panel export gets (labels, number/date
formats, bold frozen header, autofilter, widths; tables also carry their conditional
formatting and theme colors).

`params` carries the active filter / drill state and is merged into every panel's
query exactly as `POST /v1/dashboards/{id}/email` does: it overrides the keys a
panel itself declares in `data.params`, and a key no panel in scope declares is a 422
that names it. `include` narrows the export to the listed panel ids (still in spec
order); every id must be a data-bound panel in scope.

A panel that cannot be loaded does NOT fail the export: its sheet (or CSV) holds one
cell, "This panel could not be loaded: <reason>", with a fixed-vocabulary reason
(never raw warehouse or resolver text; e.g. a source needing the viewer's account
connection reads "connect your account for this data source"), and its id is listed
in `X-DVT-Failed-Panels`. A panel cut at the engine's row cap carries a comment on A1
of its sheet and is listed in `X-DVT-Truncated-Panels`.

Bounds: at most 25 panels; a per-export size limit on data cells (rows x columns)
across the whole workbook (50,000 on Gallery and Enterprise,
200,000 in the Snowflake Native App). A panel that would overshoot the remaining cell budget is cut to it
and listed as truncated; once the budget is spent the remaining panels are NOT
queried, hold the text "Skipped: this export reached its size limit..." and are
listed in `X-DVT-Skipped-Panels`. Panels are queried sequentially; no query starts
more than 18 s after the request arrived and the whole loop ends at 20 s, so a
panel not reached in time is reported as failed ("timed out before this panel
ran"). Only one dashboard export runs per organization at a time, plus a small
per-server cap on concurrent exports (a refused one gets 429 with
`Retry-After: 5`). If every exported panel failed the response is 502 and
no file is produced.

## Request body

- ExportDashboardRequest: Body for POST /v1/dashboards/{id}/export. Unknown fields are rejected (400).
  - `pageId` string, nullable: Export only this page's panels. `null` or absent exports the root panels plus every non-hidden page. An unknown page is a 422.
  - `format` 'xlsx' | 'csv': `xlsx` (one workbook, a sheet per panel) or `csv` (a zip of one CSV per panel).
  - `params` object, nullable: The active filter / drill state, merged into every panel's query (see the operation description). Same shape and limits as `EmailDashboardRequest.params`.
  - `include` string[], nullable: Panel ids to export (still emitted in spec order). Absent exports every data-bound panel in scope. Every id must be a data-bound panel in scope.

## Response `200`

The workbook (XLSX) or zip of CSVs, as an attachment.

## Other responses

- `400`: Malformed request.
- `401`: Missing, invalid, or expired session.
- `403`: The caller's role does not permit this action.
- `404`: Resource does not exist.
- `413`: The request body exceeds 64 KiB.
- `422`: The body is well-formed but this dashboard cannot honour it: an unknown `pageId` (or every page hidden with `pageId` omitted), a `params` key no panel in scope declares, an empty or oversized `include`, an `include` id that is not a data-bound panel in scope, no exportable panels, or more than 25 exportable panels (use `include` or `pageId` to narrow). Nothing is queried.
- `429`: Another dashboard export is already running for this organization, or the server is at its small concurrent-export cap. `Retry-After` says how long to wait.
- `502`: Every exported panel failed to load, so no file was produced and no `data.export` audit entry was written.

---

[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)
