writing-elixir-docs
Writing Elixir Documentation
Write and improve documentation for Elixir applications following the official Elixir documentation conventions.
Core Principles
- Elixir treats documentation as a first-class citizen — it is a contract with API users, distinct from code comments.
- Documentation is written in Markdown.
- Documentation is attached via module attributes:
@moduledoc,@doc, and@typedoc. - Code comments are for developers reading source code; documentation is for API consumers.
Module Attributes
@moduledoc — Module documentation
Place at the top of the module, immediately after defmodule:
defmodule MyApp.Hello do
@moduledoc """
This is the Hello module.
"""
end
@doc — Function/macro documentation
Place immediately before the function definition:
@doc """
Says hello to the given `name`.
Returns `:ok`.
## Examples
iex> MyApp.Hello.world(:john)
:ok
"""
def world(name) do
IO.puts("hello #{name}")
end
@typedoc — Type documentation
Place immediately before @type or @opaque definitions:
@typedoc "A color represented as {red, green, blue} tuple"
@type color :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}
Documentation Metadata
Attach metadata by passing a keyword list to @moduledoc, @doc, or @typedoc:
:since — Version annotation
@doc since: "1.3.0"
def world(name), do: IO.puts("hello #{name}")
:deprecated — Deprecation warning
@doc deprecated: "Use Foo.bar/2 instead"
def old_function, do: :ok
To also emit a compile-time warning when the function is called, add @deprecated:
@deprecated "Use Foo.bar/2 instead"
def old_function, do: :ok
:group — Grouping in sidebar/autocomplete
@doc group: "Query"
def all(query)
@doc group: "Schema"
def insert(schema)
Style Rules (MUST follow)
- First paragraph must be concise — typically one line. ExDoc uses it as the summary.
- Reference modules by full name in backticks:
`MyApp.Hello`, never`Hello`. - Reference functions by name and arity:
- Local:
`world/1` - External:
`MyApp.Hello.world/1` - Callbacks:
`c:world/1` - Types:
`t:values/0`
- Local:
- Use
##for section headers inside docs. Never use#— first-level headers are reserved for module/function names. - Place documentation before the first clause of multi-clause functions. Documentation is per function+arity, not per clause.
- Use
:sincemetadata when adding new functions or modules to annotate the version. - Include examples under a
## Examplessection whenever possible.
Function Argument Names
The compiler infers argument names for documentation. If inference is suboptimal (e.g., shows map instead of a meaningful name), declare a function head:
def size(map_with_size)
def size(%{size: size}), do: size
Doctests
Include interactive examples prefixed with iex> inside documentation. These are testable via ExUnit.DocTest:
@doc """
Adds two numbers.
## Examples
iex> MyApp.Math.add(1, 2)
3
"""
def add(a, b), do: a + b
In the corresponding test file:
defmodule MyApp.MathTest do
use ExUnit.Case, async: true
doctest MyApp.Math
end
Rules for writing doctests:
- Start each expression with
iex>and continuation lines with...> - The expected result follows on the next line without any prefix
- Separate multiple examples with a blank line
- Doctests must return values that can be compared with
==
Hiding Modules and Functions
- Use
@moduledoc falseto hide an entire module from documentation. - Use
@doc falseto hide individual functions. - Prefer moving internal functions to a module marked
@moduledoc falserather than scattering@doc false. - Prefix truly internal functions with underscores (e.g.,
__internal__/1) — the compiler won't import these.
Important: @doc false and @moduledoc false do NOT make functions private. They only hide them from documentation. Use defp for truly private functions.
Workflow
When asked to document Elixir code:
- Read the module to understand its public API.
- Add
@moduledocat the top of the module with a concise summary, then elaborate. - Add
@docto every public function and macro with:- A one-line summary as the first paragraph.
- Parameter descriptions if not obvious.
- Return value description.
- A
## Examplessection withiex>doctests where feasible.
- Add
@typedocto every public@typeand@opaque. - Add metadata (
:since,:deprecated,:group) where appropriate. - Hide internals — ensure internal modules have
@moduledoc falseand internal public functions have@doc falseor are moved to a hidden module. - Verify doctests pass by running
mix test.