Share Great Expectations Data Docs

Great Expectations gives you the right artifact and the wrong distribution. build_data_docs() renders a clean static site — suites, validation results, which expectation failed on which column, with sample unexpected values. Then it hands you a folder and the hosting problem.

The standard answer is a bucket. That answer comes with a bucket policy, a decision about whether validation results containing sample row values should be world-readable (they should not), a signed-URL or CDN layer to avoid that, and a cleanup job for the runs nobody will read again. For a team that just wants the analyst to see why last night's load failed, it's a lot of infrastructure between the failure and the person who understands it.

The pattern

Run the checkpoint, then publish the run's HTML:

import pathlib, requests, os

result = context.run_checkpoint(checkpoint_name="nightly_orders")
context.build_data_docs()

html = pathlib.Path(
    "great_expectations/uncommitted/data_docs/local_site/index.html"
).read_text()

requests.patch(
    f"https://commareports.com/api/v1/reports/{os.environ['REPORT_ID']}",
    headers={"Authorization": f"Bearer {os.environ['COMMA_API_TOKEN']}"},
    json={"title": f"Orders data quality — {result.run_id.run_time:%Y-%m-%d}",
          "html": html},
    timeout=30,
).raise_for_status()

Create the report once, keep its id in your secret store next to the scoped token (reports:write is enough), and PATCH it every run. One URL per suite; a revision per run; any two runs diffable.

What renders, honestly

Comma stores report HTML verbatim and renders it inside an iframe with sandbox="allow-scripts" and no allow-same-origin, so a report's own JavaScript runs. The constraint that bites here is different: GX's generated site is a multi-file, multi-page artifact — collapsible sections, inter-suite navigation, and the bundled CSS and JS all live in sibling files, and a revision holds one HTML document.

Upload those sibling files as assets alongside the HTML and relative src/href references are rewritten to the uploaded copies. But for a per-run validation report, a single self-contained page is the better artifact — it's what a reviewer reads, and it diffs cleanly:

  • Build a digest from the validation result JSON, not from the generated site. result.list_validation_results() gives you everything a reviewer needs: suite name, batch identifier, each expectation, its status, observed value, and unexpected-value samples. A short template turns that into one HTML table.
  • Inline the CSS you need, or upload static/ as assets. A relative link to static/styles/data_docs_custom_styles.css resolves only if that file was uploaded with the report.
  • Attach the full site. Zip local_site/ and add it as an asset — 25 MB per file, 250 MB per report — for anyone who wants to click through the original.
  • Watch the row samples. Unexpected-value samples are real customer data. That is an argument for a private or team-visible report, and against a public bucket — see the sharing model.

The part a static site can't do

A failed expectation is the start of a conversation, not the end of one. expect_column_values_to_not_be_null failing on customer_id for 0.4% of rows is either a pipeline bug or a known upstream quirk, and the person who knows which one is usually not the person who read the report.

On a published run, the analyst highlights that row and pins a thread to it. The answer lands on the expectation, survives the next thirty nightly runs as revisions accumulate, and is one click away the next time the same check goes red — instead of being a Slack message from March.

The same threads are readable by agents through Comma's MCP server, so a data agent that owns the suite can pick up "this one is expected until the CRM migration lands" without anyone re-explaining it.

Scheduling it

  • From your orchestrator — an Airflow task, a dbt post-hook, or a CI job. It's plain HTTPS.
  • Or from a routine — a scheduled report keeps the cadence next to the output.
  • Announce failures — a webhook on revision.created posts the run into Slack, so a red night is a notification rather than a discovery.

Try it

Comma is free — unlimited reports, unlimited commenters, unlimited revision history. Publish tonight's checkpoint and send the link.

Create your first report →

Related