Documentssuperset
Dashboard Export
Dashboard Export
Type
Topic
Status
Published
Created
Jul 27, 2026
Updated
Jul 27, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

Dashboard Export#

Dashboard export in Superset produces a ZIP archive of YAML files representing the dashboard, its charts, related datasets, and optional tags. The entry point is ExportDashboardsCommand in superset/commands/dashboard/export.py, which extends the generic ExportModelsCommand base class.

Chart Collection via dashboard_slices#

The Dashboard model owns its charts through the dashboard_slices association table — a simple join table with dashboard_id and slice_id columns (with a unique constraint on the pair) and cascade-delete on both FK references. The ORM relationship is declared as:

slices: list[Slice] = relationship(
    Slice, secondary=dashboard_slices, backref="dashboards"
)

During export, _export() iterates model.slices to collect chart IDs and passes them to ExportChartsCommand. Charts not referenced in position_json (orphan charts) are detected via find_chart_uuids(), which walks the position tree looking for nodes of type CHART, and are appended to the layout via append_charts().

ZIP Archive Structure#

ExportDashboardsCommand._export() yields (filename, content_callable) tuples that the API layer assembles into a ZIP:

File pathContent
dashboards/<name>.yamlDashboard metadata (export_fields + position + json_metadata)
charts/<name>.yamlPer-chart YAML (one per model.slices entry)
datasets/<name>.yamlDatasets referenced by charts (via ExportDatasetsCommand) or by native filters
metadata.yamlExport-level metadata (version, type)

The dashboard YAML serializes export_fields (dashboard_title, position_json, json_metadata, description, css, slug, certified_by, certification_details, published). position_json and json_metadata are decoded from JSON strings and re-serialized as YAML maps under the keys position and metadata respectively . Native filter datasetId references are replaced with datasetUuid for portability .

API Endpoint#

The HTTP handler lives in superset/dashboards/api.py. It accepts a RISON-encoded list of dashboard IDs via ?q=, streams the ZIP into a BytesIO buffer, and returns it via Flask's send_file():

response = send_file(buf, mimetype="application/zip", as_attachment=True, download_name=filename)

Known Bug: Stale Browser Cache on Export Endpoints#

All three export endpoints (/api/v1/dashboard/export/, /api/v1/chart/export/, /api/v1/dataset/export/) return Cache-Control: public, max-age=31536000 (1 year) because send_file() is called without a max_age argument, causing it to inherit the global SEND_FILE_MAX_AGE_DEFAULT setting from superset/config.py:

SEND_FILE_MAX_AGE_DEFAULT = int(timedelta(days=365).total_seconds())

This setting is intended for hashed static assets (JS/CSS/images) but unintentionally applies to dynamic ZIP exports. The result: if the same set of dashboard IDs is exported twice with an identical ?q= query string, the browser serves the first ZIP from disk cache — silently returning stale content even if the dashboards changed between exports .

This was identified as a regression introduced by #35584 (fix(export): replace iframe with fetch to avoid CSP frame-src violations) .

Fix: Pass max_age=0 (or set Cache-Control: no-store) explicitly in the send_file() calls in superset/dashboards/api.py, superset/charts/api.py, and superset/datasets/api.py. A fix PR was in progress as of July 2026 .

Import Counterpart#

On import, import_dashboard() in superset/commands/dashboard/importers/v1/utils.py reverses the serialization: it re-encodes position/metadata back to JSON strings, upserts the Dashboard row by UUID, and rewires chart and dataset ID references via update_id_refs().