# Share ExDoc Output — Elixir Docs Without Hex or a Bucket

Canonical: https://commareports.com/share-exdoc
Published: 2026-09-14

> mix docs writes a doc/ folder with search and a sidebar. HexDocs only takes published packages. Publish the folder to Comma for one private URL per app, with comments.

# Share ExDoc output

`mix docs` is one of the better documentation experiences in any ecosystem —
module docs, `@doc` examples that are also tests, guides, a search box that
works. It writes the whole thing to `doc/` and then the ecosystem assumes the
next step is publishing to Hex.

For an internal umbrella app, a client project, or a release candidate, that
assumption is wrong in both directions: you do not want it public, and it is
not a package.

## Build it

```bash
MIX_ENV=docs mix docs
```

```
doc/
├── index.html
├── MyApp.Accounts.html      # one page per module
├── readme.html  changelog.html
├── dist/                    # js, css, the search index
└── assets/
```

Open `index.html` from the filesystem and the pages render but the search box
does not: ExDoc fetches its index over XHR, which a browser blocks from a
`file://` origin. It is the same
[origin problem](/fix/plotly-chart-not-showing-in-html) that breaks most
generated docs when they are zipped and emailed.

## Drop the folder in

Drag `doc/` (or a zip of it) into [Comma](https://commareports.com/):

- `index.html` becomes the **report body** — the page carrying the comment
  layer.
- Module pages, guides, `dist/` and `assets/` upload alongside it, and relative
  references are rewritten to the uploaded copies, so **search works** and the
  sidebar navigates.
- Scripts run inside a sandboxed iframe (`allow-scripts`, no
  `allow-same-origin`).

Access per report: private, your team, anyone signed in at your domain, or
anyone with the link — see [sharing & access control](/docs/sharing).

## From CI

```bash
MIX_ENV=docs mix docs

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 doc/index.html \
        --arg t "MyApp — $(mix run -e 'IO.puts Mix.Project.config()[:version]')" \
        '{title: $t, html: $html}')"
```

Supporting pages go up through `POST /api/v1/reports/$REPORT_ID/assets` as
base64 — one call per file, worth scripting once. See the
[API reference](/docs/api) and [publishing from CI](/docs/ci).

## An umbrella is the case that needs this most

An umbrella project generates docs per app, and the thing nobody has is a
single readable view of the boundary between them — which context owns which
schema, and which public function another app is allowed to call. Publishing
each app's `doc/` as its own report gives you that as a set of links, and
comments anchored on a specific `@doc` are where the boundary argument actually
belongs. See [commenting on HTML](/comment-on-html).

## Limits

- **Entry HTML: 5 MB.** Assets: 25 MB per file, **250 MB and 500 files total**.
  A large app with many modules can approach 500 pages — `:filter_modules` or
  splitting per umbrella app keeps it under.
- **Scripts run, sandboxed** — no same-origin access.
- **60 requests/minute per token.**

## Try it

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

**[Publish ExDoc output →](https://commareports.com/)**

### Related

- [Share Scaladoc](/share-scaladoc) · [Share YARD docs](/share-yard-docs)
- [Share Javadoc](/share-javadoc) · [Share Sphinx docs](/share-sphinx-docs)
- [Share an HTML folder](/share-html-folder) · [GitHub Pages alternatives](/alternatives/github-pages-alternatives)
