Write or review Elixir documentation following the project's style guide. Use when writing @moduledoc, @doc, guides, or reviewing existing docs for quality. Invoke with "/write-docs" or when the user asks to write, improve, or review documentation.
/write-docs)Write or review documentation for $ARGUMENTS, following the conventions below.
Distilled from studying Phoenix, Ecto, Plug, Req, Ash, Oban, Tesla, Jason, NimbleOptions, and Elixir's official writing-documentation guide.
Documentation is a contract with the library consumer. It tells them what things do and how to use them. Code tells them how it works. Comments tell them why.
Lead with one plain-English sentence that tells the reader what the module
is or does for them. This sentence becomes the summary in ExDoc and
mix docs. Keep it under ~120 characters.
Patterns to follow:
Anti-patterns:
Good openers from top libraries:
| Library | Opening |
|---|---|
| Plug.Conn | "The Plug connection." |
| Ecto.Changeset | "Changesets allow filtering, type casting, validation, and constraints when manipulating structs." |
| Req | "Req is a batteries-included HTTP client for Elixir." |
| Ash | "The primary interface to call actions and interact with resources." |
After the opening, use ## sections. Common sections in order:
## Examples or ## Usage (show the happy path first)## Fields / ## Options / ## Configuration## Sections for major conceptsFor facade modules that tie together multiple submodules, list them early:
@moduledoc """
Ltix handles the LTI 1.3 OIDC launch flow. It is built around 4 components:
* `Ltix.Registration` - what the tool knows about a platform
* `Ltix.StorageAdapter` - behaviour your app implements
* `Ltix.LaunchContext` - the validated output of a launch
* `Ltix.LaunchClaims` - structured data from the ID Token
"""
One sentence, active voice, present tense. Describe what the function does for the caller, not how it works internally.
Good:
Bad:
For simple functions, parameters are self-evident from the typespec and argument names. Don't repeat what's obvious. For complex functions, document parameters inline or under a short paragraph:
@doc """
Inserts a struct defined via `Ecto.Schema` or a changeset.
When given a struct, it is converted to a changeset with all non-nil
fields as changes. When given a changeset, all changes in the changeset
are sent to the database.
"""
When the compiler infers a bad argument name from pattern matching, declare a function head to fix it:
def size(map_with_size)
def size(%{size: size}), do: size
Use ## Options with a bullet list. NimbleOptions docs generate
automatically; for hand-written options:
@doc """
...
## Options
* `:role` - filter by role. Accepts a role atom (e.g., `:learner`),
URI string, or `%Role{}` struct.
* `:per_page` - page size hint. The platform may return more or fewer.
* `:max_members` - safety limit for eager fetch (default: `10_000`).
Set to `:infinity` to disable.
"""
Style rules for options:
:option_name in backtick-code(default: \value`)` * `:auth` - sets request authentication.
- `{:basic, userinfo}` - Basic HTTP authentication
- `{:bearer, token}` - Bearer authentication
Document return types in the opening paragraphs, not as a separate section:
@doc """
Fetches a single result from the query.
Returns `nil` if no result is found. Raises if more than one entry.
"""
Keep bang docs minimal. Reference the non-bang variant:
@doc """
Same as `authenticate/2` but raises on error.
"""
## Examples heading## Examples
iex> conn = assign(conn, :hello, :world)
iex> conn.assigns[:hello]
:world
iex>:## Examples
{:ok, client} = MembershipsService.authenticate(context)
{:ok, roster} = MembershipsService.get_members(client)
...> continuation prefix`MyModule` or `m:MyModule``function/arity` (local) or `Module.function/arity``c:callback/arity``t:type/arity``m::erlang_mod`[text](`Module.function/1`)[Guide Title](guide-name.md)`m:Module#module-section-name`Call out important behavior inline, not in a separate "Notes" section:
@doc """
Sends a response with the given status and body.
This function does not halt the connection, so if subsequent plugs
try to send another response, it will error out. Use `halt/1` after
this function if you want to halt the plug pipeline.
"""
| Pattern | When to use | Example |
|---|---|---|
| Signpost | Orientation, landing pages | Phoenix overview |
| Linear tutorial | Teaching a workflow | Ecto Getting Started |
| Feature showcase | Introducing a library | Req README |
| Task guide | Procedural how-to | Oban installation |
# lib/my_app_web/router.exUse tables for comparisons, quick reference, and callback summaries:
| Callback | Called during | Purpose |
|---|---|---|
| `get_registration/2` | Login initiation | Look up a platform |
Use ExDoc admonitions sparingly for genuinely important warnings:
> #### Production {: .warning}
>
> The in-memory Agent is fine for development. In production, store
> nonces in your database with a TTL.
Types: .info, .warning, .error, .tip, .neutral
`Ltix.LaunchContext`[Error Handling](error-handling.md)Use @doc since: and @moduledoc since: to annotate when things were
added to the public API:
@doc since: "0.2.0"
Use @doc deprecated: for soft deprecation (warning in docs, no
compile warning):
@doc deprecated: "Use authenticate/2 instead"
Use @doc group: to group functions in ExDoc sidebar:
@doc group: "Query"
def get_members(client, opts \\ [])
Use @moduledoc false for modules that consumers never interact with
directly. These are implementation details behind a facade. Examples:
parsers, internal helpers, protocol implementations.
Still document functions with @doc if the module is complex enough that
developers on the project benefit from it, but the moduledoc stays false.
Spec references belong in code comments next to the implementing code,
NOT in user-facing docs. Use the anchor mappings in spec-anchors.md.
Format: # [Core 5.1.2](https://www.imsglobal.org/spec/lti/v1p3/#lti-domain-model)
Exception: modules where the spec is directly relevant to the library
consumer (e.g., StorageAdapter) may include spec refs in @moduledoc/@doc.
Before finalizing documentation, verify:
:key - description format@moduledoc false