# How to Share a Plotly Dash App Without Deploying a Server

Canonical: https://commareports.com/share-dash-app
Published: 2026-09-04

> Dash callbacks run in Flask, so there is no static export of a Dash app. When the reader only needs the figures, the same Plotly charts render to one self-contained HTML file you can publish and comment on.

# How to share a Plotly Dash app

Dash sits in an awkward spot. It looks like a dashboard you could export —
the layout is declarative, the charts are Plotly, and Plotly figures serialize
to standalone HTML happily. But the interactivity is not in the figures. It is
in the callbacks.

## Why there is no static Dash export

Every `@callback` runs in the Flask process. When a reader changes the
dropdown, the browser POSTs to `/_dash-update-component` and waits for Python
to return new component props. Remove the server and you get a page that
renders its initial state and then ignores every control — which is a worse
artifact than an honest static report, because it looks alive and is not.

So there are two real paths, and they serve different readers.

## Path one: deploy it

Any host that runs a Python web process works — Cloud Run, Fly, Render, an
instance behind gunicorn, Dash Enterprise if you want the managed version with
auth and deployment tooling attached. Put authentication in front of it,
because a dashboard wired to your warehouse is a query surface.

Do this when readers genuinely drive the controls. If two people a week change
the date filter and everyone else looks at the default view, you are operating
a service for an audience of two.

## Path two: publish the figures

Most Dash apps are, in practice, a fixed set of charts with a few filters
nobody touches. That version renders to a single file, and the charts stay
interactive, because Plotly's zoom, hover, and legend behaviour is client-side
JavaScript — only the Python round-trip disappears.

```python
import plotly.io as pio
from app import build_figures          # reuse the app's own figure builders

figs = build_figures(load_data())
body = "".join(
    pio.to_html(f, full_html=False, include_plotlyjs="inline" if i == 0 else False)
    for i, f in enumerate(figs)
)

html = f"""<!doctype html><meta charset="utf-8">
<style>body{{font:15px/1.6 system-ui;margin:2rem auto;max-width:70rem}}
h1{{font-size:1.5rem}}</style>
<h1>Operations dashboard — {date.today():%d %b %Y}</h1>{body}"""

open("dashboard.html", "w").write(html)
```

Two details carry the whole thing. `include_plotlyjs="inline"` on the first
figure embeds the library so the page fetches nothing at view time — a linked
CDN is the single most common reason a shared chart renders blank on someone
else's network. And `False` on the rest avoids shipping the library four
times, which is the difference between a 4 MB file and a 20 MB one.

Then publish it, and keep publishing to one id:

```bash
curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
  -H "Authorization: Bearer $COMMA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile html dashboard.html \
        --arg title "Operations — $(date +%F)" '{title: $title, html: $html}')"
```

Comma runs report HTML verbatim with scripts enabled, so the Plotly charts
behave exactly as they did in the app. A [routine](/features/routines) can run
the refresh on a schedule if you would rather not own a cron.

## What you get that the app never had

**A URL with no uptime.** Nothing to keep running, nothing to wake up,
no cold start for the person who opens it at 11pm.

**Access without accounts.** Visibility is a setting on the report:
[private, team, domain-gated or link](/docs/sharing).

**Anchored discussion.** A reader highlights the throughput dip and pins "this
is the maintenance window, expected." That thread persists across every
revision, so the same question does not get asked next month. A live dashboard
has nowhere to keep that — the state it shows is always now, and the reasoning
about last Tuesday lives in someone's memory.

## Which to build

If the filters are the product, deploy the app and put auth in front of it.
If the charts are the product and the filters are decoration, publish the
figures on a schedule and delete the deployment. Most teams discover they are
in the second case once they look at who actually clicks anything.

## Try it

Comma is free — unlimited reports, unlimited commenters, unlimited revision
history.

**[Create your first report →](https://commareports.com/)**

### Related

- [Share a Plotly HTML chart](/share-plotly-html)
- [Share a Streamlit app](/share-streamlit-app)
- [Share a Panel app](/share-panel-app)
