elixir-writing-docs
Guides writing Elixir documentation with @moduledoc, @doc, @typedoc, doctests, cross-references, and metadata. Use when adding or improving documentation in .ex files.
git clone --depth 1 https://github.com/existential-birds/beagle /tmp/elixir-writing-docs && cp -r /tmp/elixir-writing-docs/plugins/beagle-elixir/skills/elixir-writing-docs ~/.claude/skills/elixir-writing-docsSKILL.md
# Elixir Writing Docs
## Quick Reference
| Topic | Reference |
|-------|-----------|
| Doctests: syntax, gotchas, when to use | [references/doctests.md](references/doctests.md) |
| Cross-references and linking syntax | [references/cross-references.md](references/cross-references.md) |
| Admonitions, formatting, tabs | [references/admonitions-and-formatting.md](references/admonitions-and-formatting.md) |
## First-Line Summary Rule
ExDoc and tools like `mix docs` extract the first paragraph of `@moduledoc` and `@doc` as a summary. Keep the opening line concise and self-contained.
```elixir
# GOOD - first line works as a standalone summary
@moduledoc """
Handles payment processing through Stripe and local ledger reconciliation.
Wraps the Stripe API client and ensures each charge is recorded in the
local ledger before returning a confirmation to the caller.
"""
# BAD - first line is vague, forces reader to continue
@moduledoc """
This module contains various functions related to payments.
It uses Stripe and also updates the ledger.
"""
```
The same rule applies to `@doc`:
```elixir
# GOOD
@doc """
Charges a customer's default payment method for the given amount in cents.
Returns `{:ok, charge}` on success or `{:error, reason}` when the payment
gateway rejects the request.
"""
# BAD
@doc """
This function is used to charge a customer.
"""
```
## @moduledoc Structure
A well-structured `@moduledoc` follows this pattern:
```elixir
defmodule MyApp.Inventory do
@moduledoc """
Tracks warehouse stock levels and triggers replenishment orders.
This module maintains an ETS-backed cache of current quantities and
exposes functions for atomic stock adjustments. It is designed to be
started under a supervisor and will restore state from the database
on init.
## Examples
iex> {:ok, pid} = MyApp.Inventory.start_link(warehouse: :east)
iex> MyApp.Inventory.current_stock(pid, "SKU-1042")
{:ok, 350}
## Configuration
Expects the following in `config/runtime.exs`:
config :my_app, MyApp.Inventory,
repo: MyApp.Repo,
low_stock_threshold: 50
"""
end
```
**Key points:**
- First paragraph is the summary (one to two sentences).
- `## Examples` shows realistic usage. Use doctests when the example is runnable.
- `## Configuration` documents required config keys. Omit this section if the module takes no config.
- Use second-level headings (`##`) only. First-level (`#`) is reserved for the module name in ExDoc output.
### Documenting Behaviour Modules
When defining a behaviour, document the expected callbacks:
```elixir
defmodule MyApp.PaymentGateway do
@moduledoc """
Behaviour for payment gateway integrations.
Implementations must handle charging, refunding, and status checks.
See `MyApp.PaymentGateway.Stripe` for a reference implementation.
## Callbacks
* `charge/2` - Initiate a charge for a given amount
* `refund/2` - Refund a previously completed charge
* `status/1` - Check the status of a transaction
"""
@callback charge(amount :: pos_integer(), currency :: atom()) ::
{:ok, transaction_id :: String.t()} | {:error, term()}
@callback refund(transaction_id :: String.t(), amount :: pos_integer()) ::
:ok | {:error, term()}
@callback status(transaction_id :: String.t()) ::
{:pending | :completed | :failed, map()}
end
```
## @doc Structure
```elixir
@doc """
Reserves the given quantity of an item, decrementing available stock.
Returns `{:ok, reservation_id}` when stock is available, or
`{:error, :insufficient_stock}` when the requested quantity exceeds
what is on hand.
## Examples
iex> MyApp.Inventory.reserve("SKU-1042", 5)
{:ok, "res_abc123"}
iex> MyApp.Inventory.reserve("SKU-9999", 1)
{:error, :not_found}
## Options
* `:warehouse` - Target warehouse atom. Defaults to `:primary`.
* `:timeout` - Timeout in milliseconds. Defaults to `5_000`.
"""
@spec reserve(String.t(), pos_integer(), keyword()) ::
{:ok, String.t()} | {:error, :insufficient_stock | :not_found}
def reserve(sku, quantity, opts \\ []) do
# ...
end
```
**Guidelines:**
- State what the function does, then what it returns.
- Document each option in a bulleted `## Options` section when the function accepts a keyword list.
- Place `@spec` between `@doc` and `def`. This is the conventional ordering.
- Include doctests for pure functions. Skip them for side-effecting functions (see [references/doctests.md](references/doctests.md)).
## @typedoc
Document custom types defined with `@type` or `@opaque`:
```elixir
@typedoc """
A positive integer representing an amount in the smallest currency unit (e.g., cents).
"""
@type amount :: pos_integer()
@typedoc """
Reservation status returned by `status/1`.
* `:held` - Stock is reserved but not yet shipped
* `:released` - Reservation was cancelled and stock restored
* `:fulfilled` - Items have shipped
"""
@type reservation_status :: :held | :released | :fulfilled
@typedoc """
Opaque handle returned by `connect/1`. Do not pattern-match on this value.
"""
@opaque connection :: %__MODULE__{socket: port(), buffer: binary()}
```
For `@opaque` types, the `@typedoc` is especially important because callers cannot inspect the structure.
## Metadata
### @doc since and @doc deprecated
```elixir
@doc since: "1.3.0"
@doc """
Transfers stock between two warehouses.
"""
def transfer(from, to, sku, quantity), do: # ...
@doc deprecated: "Use transfer/4 instead"
@doc """
Moves items between locations. Deprecated in favor of `transfer/4`
which supports cross-region transfers.
"""
def move_stock(from, to, sku, quantity), do: # ...
```
You can combine metadata and the docstring in one attribute:
```elixir
@doc since: "2.0.0", deprecated: "Use bulk_reserve/2 instead"
@doc """
Reserves multiple items in a single call.
"""
def batch_reserve(items), do: # ...
```
`@moduledoc since:` works the same way for modules:
```elixir
@moduledoc sincetag and push a release after the release PR is merged
create a release PR (auto-detects previous tag)
Guides architectural decisions for Deep Agents applications. Use when deciding between Deep Agents vs alternatives, choosing backend strategies, designing subagent systems, or selecting middleware approaches.
Reviews Deep Agents code for bugs, anti-patterns, and improvements. Use when reviewing code that uses create_deep_agent, backends, subagents, middleware, or human-in-the-loop patterns. Catches common configuration and usage mistakes.
Implements agents using Deep Agents. Use when building agents with create_deep_agent, configuring backends, defining subagents, adding middleware, or setting up human-in-the-loop workflows.
Guides architectural decisions for LangGraph applications. Use when deciding between LangGraph vs alternatives, choosing state management strategies, designing multi-agent systems, or selecting persistence and streaming approaches.
Reviews LangGraph code for bugs, anti-patterns, and improvements. Use when reviewing code that uses StateGraph, nodes, edges, checkpointing, or other LangGraph features. Catches common mistakes in state management, graph structure, and async patterns.
Implements stateful agent graphs using LangGraph. Use when building graphs, adding nodes/edges, defining state schemas, implementing checkpointing, handling interrupts, or creating multi-agent systems with LangGraph.