# Slack Won't Preview an HTML File — Why, and What to Post Instead

Canonical: https://commareports.com/fix/slack-wont-preview-html-file
Published: 2026-09-07

> Upload an HTML report to Slack and you get a download card, not a preview. The security reason Slack refuses to render it, what the snippet view shows, and the link that unfurls properly.

# Slack shows a download card, not the report

You drag `coverage/index.html` into the channel. Slack posts a file card
with a paperclip and a **Download** button. Nobody clicks it.

## Why Slack refuses

Rendering uploaded HTML in-client means executing uploaded JavaScript
inside a Slack origin, with a logged-in session attached to it. That is
a stored-XSS surface aimed at everyone in the workspace, and no messaging
product ships it. GitHub declines for
[the same reason](/fix/github-actions-artifact-html-not-viewable), and
so does every mail client.

So `.html` is an opaque file. For small ones you get the **snippet**
view — escaped source, truncated. It is a way to read markup, not a way
to read a report.

## Why the download path is worse than it looks

The colleague who does click Download gets a file in `~/Downloads` and
opens it from `file://`, which is where the second wave of problems
starts:

- Relative asset references 404, so the report loses its styling —
  [the five causes](/html-report-broken-css).
- Reports that load results over XHR render blank outright:
  [Allure](/fix/allure-report-blank-page),
  [Playwright](/fix/playwright-report-blank), several coverage UIs.
- A single `index.html` pulled out of its directory was never going to
  work at all.

So the file that "was shared" often cannot be read by the person it was
shared with, and neither of you finds out.

## What actually gets read

Post a link. A published report unfurls in the channel with its title
and description, opens in one click, and renders identically on a phone
and a desktop.

The part that matters after the click: the discussion goes **in the
report**. Someone selects the failing test, or the table cell with the
suspicious number, and comments there. That thread stays anchored to
that content across revisions — where a channel thread scrolls out of
reach by Thursday and nobody can reconstruct which chart "this looks
wrong" was about. See
[comment on HTML](/comment-on-html) and
[share an HTML report in Slack](/share-html-report-in-slack).

## Automating it

From CI, publish at the end of the job and post the URL:

```yaml
- name: Publish and announce
  if: always()
  run: |
    curl -sS -X PATCH \
      "https://commareports.com/api/v1/reports/$COMMA_REPORT_ID" \
      -H "Authorization: Bearer $COMMA_API_TOKEN" \
      -H "Content-Type: application/json" \
      --data @<(jq -Rs '{html: .}' htmlcov/index.html)
    curl -sS -X POST "$SLACK_WEBHOOK_URL" \
      -H "Content-Type: application/json" \
      --data "{\"text\":\"Coverage for \`$GITHUB_SHA\`: https://commareports.com/p/$COMMA_REPORT_ID\"}"
```

Because the report id is stable, a **pinned** message keeps working —
each run appends a revision at the same URL rather than creating a new
link to re-pin. Setup in [publish from CI](/docs/ci).

Agents can do the same thing over MCP: an agent that just finished an
analysis publishes it and hands back a link, rather than pasting three
thousand tokens of markup into the channel —
[where should my agent post](/agents/where-should-my-agent-post).

## Try it

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

**[Publish a report and post the link →](https://commareports.com/)**

### Related

- [Share an HTML report in Slack](/share-html-report-in-slack)
- [Share an HTML report in Teams](/share-html-report-in-teams)
- [Stop screenshotting reports](/stop-screenshotting-reports)
- [Comment on HTML](/comment-on-html)
