elixir-lang/ex_doc
> The documentation generator for the BEAM — it renders the doc chunks of > compiled Elixir and Erlang projects into the HTML you read on hexdocs.pm.
GitHub repo · Official docs · License: Apache-2.0 (with output-attribution clause)
Overview
ExDoc generates HTML, EPUB, and (since v0.40) Markdown documentation for Elixir and Erlang projects. It started at Plataformatec in March 2012 — two years before Elixir 1.0 — and is maintained by the Elixir core team. Its ~1.6k stars wildly understate its reach: ExDoc renders the documentation for essentially every package on hex.pm (docs are built with it on publish and hosted at hexdocs.pm), and since OTP 27 (2024) it builds the official Erlang/OTP documentation as well[^1]. It is invisible infrastructure, consumed daily by people who never visit the repo.
The defining design decision is that ExDoc does not parse your source code. It reads the "Docs" chunk the compiler embeds in .beam files, standardized as EEP 48[^2] and adopted by Elixir 1.7 (2018)[^3]; EDoc gained chunk output in OTP 24, which made ExDoc usable for Erlang via the rebar3_ex_doc plugin[^4]. The upside is language-neutrality across the BEAM and perfect fidelity with what the compiler saw; the downside is that you must compile before you can document, and stripped builds yield empty output.
The tradeoff to be aware of: after 14 years the project is still on 0.x versioning, and it uses that license to ship breaking changes between minor versions (v0.40 revamped the formatter API[^5]).
Getting Started
Requires Elixir v1.15+. Add to mix.exs (dev-only, never a runtime dep):
def deps do
[
{:ex_doc, "~> 0.34", only: :dev, runtime: false, warn_if_outdated: true}
]
end
Then mix deps.get && mix docs — output lands in doc/. Set :source_url and a docs: [main: "readme", extras: ["README.md"]] block in project/0 for source links and guide pages. Erlang projects use rebar3_ex_doc; an escript CLI (mix escript.install hex ex_doc) takes compiled ebin directories directly.
Architecture / How It Works
The pipeline is: retriever → autolinker → Markdown render → highlight → formatters.
- Retriever — walks compiled
.beamfiles and extracts EEP 48 doc
chunks, typespecs, and callbacks. Parallelized in v0.37, which made generation 20–30x faster on large projects[^5].
- Autolinker — resolves backtick references (`
MyModule.fun/1`,
t:type/0, c:callback/1) against the project and its dependencies, rewriting cross-package links to hexdocs.pm URLs. Invalid references warn at build time (since v0.30), which doubles as a docs linter.
- Markdown — parsed by
earmark_parser, with ExDoc-specific extensions:
admonition blocks, tabsets, .cheatmd cheatsheets, .livemd livebooks.
- Highlighting — done at build time by Makeup, a pure-Elixir lexer
family[^6]; ExDoc dropped highlight.js (and jQuery) in v0.24. Only Elixir and Erlang lexers ship by default — other languages need an explicit makeup_LANGUAGE dev dependency or code blocks render unhighlighted.
- Formatters — HTML (a fully static site: client-side full-text search
over a prebuilt index, Swup page transitions, night mode), EPUB, and since v0.40 Markdown plus an llms.txt generated by default with a "Copy Markdown" button per page — an explicit bid to be agent-readable[^5].
The frontend JS/CSS is built with npm but committed precompiled under formatters/, so downstream users never need Node. The version dropdown and the g go-to-any-package shortcut are hexdocs.pm conventions that auto-configure only when hosted there — tool and platform are co-designed.
Production Notes
- You must compile first. Docs come from
.beamchunks: a broken build
means no docs, and release tooling that strips chunks silently produces empty pages.
- The
erlang-devfootgun.earmark_parseruses yecc; Debian-family
distros ship Erlang dev headers separately. The failure mode is a cryptic yeccpre.hrl: no such file or directory — install erlang-dev.
- 0.x means minor = major. Pin with
~> 0.NNand read the changelog on
bumps: v0.34 raised the Elixir floor, v0.39 started validating :extras (previously-tolerated garbage now raises), v0.40 broke the formatter API.
- The license clause is not plain Apache-2.0. Generated documentation
must carry a visible link back to the ExDoc repository on every rendered HTML page[^7]. GitHub reports the license as NOASSERTION for this reason. If you white-label docs for a commercial product, have legal read this.
- Self-hosting is fine, but second-class. Version dropdowns and
cross-package autocomplete assume hexdocs.pm. Platforms that strip .html extensions needed a compatibility fix (v0.34); check redirects.
- Pre-0.37 was slow on big codebases. If generation takes minutes,
upgrade — the parallelized retriever is the biggest win in years.
- Maintenance is healthy: pushed within the last day, 17 open issues, steady
release cadence (v0.40.3 in May 2026) under the Elixir org.
When to Use / When Not
Use when:
- You are publishing any Elixir library — it is the ecosystem standard and
what mix hex.publish expects; deviating costs you hexdocs.pm.
- You maintain an Erlang project and want modern, searchable docs instead of
raw EDoc output — rebar3_ex_doc is the path OTP itself validated.
- You want API reference, guides, cheatsheets, and livebooks in one build
colocated with the code, with cross-references checked at build time.
Avoid when:
- Your project is not on the BEAM — there are no doc chunks to read; this is
a hard boundary, not a missing feature.
- You want a prose-first product docs site with heavy theming and custom
layouts — ExDoc customization stops at logo, CSS/JS injection, and grouping.
- You need PDF output — HTML, EPUB, and Markdown are the only formatters.
Alternatives
- sphinx-doc/sphinx — use instead for Python or polyglot projects needing
PDF/LaTeX output and a mature extension ecosystem.
- mkdocs/mkdocs — use instead when the docs are prose-first and independent
of any code's API surface.
- TypeStrong/typedoc — the equivalent role for TypeScript libraries.
- rust-lang/mdBook — use instead for book-style guides with no API
reference component.
- asciidoctor/asciidoctor — use instead when Markdown's expressiveness runs
out.
Within Elixir itself there is no serious alternative — ExDoc's hexdocs.pm integration makes it a de facto monopoly, for better and worse.
History
| Version | Date | Notes | |---------|------|-------| | 0.1 | 2012-03 | Initial release at Plataformatec; predates Elixir 1.0 by two years. | | 0.24 | 2021-03-16 | Dropped jQuery; replaced highlight.js with build-time Makeup[^5]. | | 0.29 | 2022-10-19 | Cheatsheets (.cheatmd). | | 0.30 | 2023-07-07 | Tabsets; warnings on invalid link references. | | 0.34 | 2024-05-30 | Elixir 1.13 floor; .html-stripping host compatibility. | | 0.37 | 2025-02-05 | Parallelized retriever, 20–30x faster generation. | | 0.40.0 | 2026-01-20 | Markdown formatter; llms.txt by default; formatter API revamp (breaking). | | 0.40.3 | 2026-05-21 | Current release; OTP 29 native record autolinking. |
References
[^1]: Erlang/OTP 27 highlights — documentation migrated to Markdown + ExDoc. https://www.erlang.org/blog/otp-27-highlights/ [^2]: EEP 48: Documentation storage and format. https://www.erlang.org/eeps/eep-0048 [^3]: "Elixir v1.7 released" — EEP 48 adoption. 2018-07-25. https://elixir-lang.org/blog/2018/07/25/elixir-v1-7-0-released/ [^4]: rebar3_ex_doc — ExDoc plugin for rebar3. https://github.com/starbelly/rebar3_ex_doc [^5]: ExDoc CHANGELOG. https://github.com/elixir-lang/ex_doc/blob/main/CHANGELOG.md [^6]: Makeup — syntax highlighting for the BEAM. https://github.com/elixir-makeup/makeup [^7]: ExDoc README, License section (attribution requirement on rendered output). https://github.com/elixir-lang/ex_doc#license
Tags
elixir, erlang, beam, documentation, static-site-generator, api-reference, hexdocs, developer-tools, markdown, epub