# Share an HTML Report From Dagster

Canonical: https://commareports.com/ci/dagster-html-report
Published: 2026-09-09

> Dagster stores your HTML as a materialization or an IO-managed asset behind the webserver login. Publish it from the asset so the stakeholder waiting on the data gets a URL.

# Share an HTML report from Dagster

Dagster is unusually good at knowing what happened: the asset materialized, the
check passed, here is the metadata. It is a data platform, though, and the
report your pipeline produced still has to reach a person who does not use it.

- **Materialization metadata is a summary, not a document.** Markdown metadata
  on an asset is genuinely useful in the UI, and it is not where a 40-row
  quality table with charts belongs.
- **The webserver is behind a login.** Usually an internal one. The analyst,
  the finance partner and the customer waiting on the number are outside it.
- **The IO manager writes to storage, not to an address.** A bucket path is
  something your code can read. Turning it into something a person can read
  means handing out bucket credentials.
- **The asset is fresh, and nobody can see it.** Which is the failure mode
  worth fixing, because freshness is the thing Dagster gets right.

## Publish from the asset

Publishing is one call at the end of the asset function, and the URL is worth
attaching as metadata so the link is discoverable from the UI too:

```python
import json, os, urllib.request
import dagster as dg

REPORT_ID = "rep_…"  # created once; PATCHed from every materialization

@dg.asset
def revenue_quality_report(context: dg.AssetExecutionContext) -> dg.MaterializeResult:
    html = build_report_html()
    body = json.dumps({"title": "Revenue data quality", "html": html}).encode()
    req = urllib.request.Request(
        f"https://commareports.com/api/v1/reports/{REPORT_ID}",
        data=body,
        method="PATCH",
        headers={
            "Authorization": f"Bearer {os.environ['COMMA_API_TOKEN']}",
            "Content-Type": "application/json",
        },
    )
    urllib.request.urlopen(req).read()
    return dg.MaterializeResult(
        metadata={"report": dg.MetadataValue.url(f"https://commareports.com/p/{REPORT_ID}")}
    )
```

Two things make it work in practice:

- **`PATCH` a saved id, don't `POST`.** One report per asset, a revision per
  materialization. That is what lets someone compare this run to last Tuesday's
  instead of hunting through a folder of dated links.
- **Publish on failure too.** Wire a run-failure or asset-check-failure sensor
  that publishes the digest. The interesting report is the one from the run
  that went wrong.

Scope the token to `reports:write` and keep it in a resource backed by your
secrets manager — see [API tokens](/docs/api-tokens).

## What the reader gets

- **A URL and nothing to install.** No Dagster login, no bucket credentials,
  no seat.
- **The document, rendered.** Great Expectations output, a Sweetviz profile, a
  Plotly figure — see
  [sharing Great Expectations data docs](/share-great-expectations-data-docs)
  and [sharing an EDA report](/share-eda-report).
- **A comment on the failing check**, anchored to that row — see
  [commenting on HTML](/comment-on-html).
- **A revision history** that answers "when did this metric drift", which is
  the question a data platform gets asked most.

## Worth knowing

- **Partitioned assets.** One report per partition is a lot of links; usually
  the right shape is one report per asset, PATCHed by a summary step, with the
  partition breakdown inside it.
- **Rate limit: 60 requests/minute per token.** A fan-out over hundreds of
  partitions should publish once, from downstream.
- **HTML body: 5 MB.** Publish the aggregate; attach the extract.
- **Multi-file output** — dbt docs, a profiling directory — needs the folder
  treatment, see [sharing an HTML folder](/share-html-folder) and
  [sharing dbt docs](/share-dbt-docs).

## Try it

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

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

### Related

- [Publish from CI](/ci) — the general pipeline pattern
- [Share an Airflow report](/ci/airflow-html-report) ·
  [Share a Prefect flow report](/ci/prefect-html-report)
- [Share a Great Expectations data doc](/share-great-expectations-data-docs) ·
  [Scheduled HTML reports](/features/routines/scheduled-html-reports)
