Erlang Otp Behaviors
benchflow-ai/skillsbench
A skill your agent uses when oTP behaviors including genserver for stateful processes, genstatem for state machines, supervisors for fault tolerance, genevent for event handling, and building…
A skill your agent uses when writing GenServer, Supervisor, Task, Agent, or Registry code — init/handlecontinue, call vs cast, supervision strategies, process naming.
$ npx skills add j-morgan6/elixir-phoenix-guide --skill otp-essentials -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install j-morgan6/elixir-phoenix-guide otp-essentials --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/j-morgan6/elixir-phoenix-guide.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/otp-essentials .claude/skills/otp-essentials && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "otp-essentials" agent skill from https://github.com/j-morgan6/elixir-phoenix-guide/tree/main/skills/otp-essentials into .claude/skills/otp-essentials/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "otp-essentials", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/j-morgan6/elixir-phoenix-guide/tree/main/skills/otp-essentialsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add j-morgan6/elixir-phoenix-guide --skill otp-essentials -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install j-morgan6/elixir-phoenix-guide otp-essentials --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/j-morgan6/elixir-phoenix-guide.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/otp-essentials .agents/skills/otp-essentials && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "otp-essentials" agent skill from https://github.com/j-morgan6/elixir-phoenix-guide/tree/main/skills/otp-essentials into .agents/skills/otp-essentials/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "otp-essentials", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add j-morgan6/elixir-phoenix-guide --skill otp-essentials -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install j-morgan6/elixir-phoenix-guide otp-essentials --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/j-morgan6/elixir-phoenix-guide.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/otp-essentials .cursor/skills/otp-essentials && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "otp-essentials" agent skill from https://github.com/j-morgan6/elixir-phoenix-guide/tree/main/skills/otp-essentials into .cursor/skills/otp-essentials/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "otp-essentials", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/j-morgan6/elixir-phoenix-guide.git --path skills/otp-essentials--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add j-morgan6/elixir-phoenix-guide --skill otp-essentials -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install j-morgan6/elixir-phoenix-guide otp-essentials --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/j-morgan6/elixir-phoenix-guide.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/otp-essentials .gemini/skills/otp-essentials && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "otp-essentials" agent skill from https://github.com/j-morgan6/elixir-phoenix-guide/tree/main/skills/otp-essentials into .gemini/skills/otp-essentials/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "otp-essentials", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install j-morgan6/elixir-phoenix-guide otp-essentialsInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add j-morgan6/elixir-phoenix-guide --skill otp-essentials -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/j-morgan6/elixir-phoenix-guide.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/otp-essentials .github/skills/otp-essentials && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "otp-essentials" agent skill from https://github.com/j-morgan6/elixir-phoenix-guide/tree/main/skills/otp-essentials into .github/skills/otp-essentials/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "otp-essentials", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add j-morgan6/elixir-phoenix-guide --skill otp-essentials -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install j-morgan6/elixir-phoenix-guide otp-essentials --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/j-morgan6/elixir-phoenix-guide.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/otp-essentials .opencode/skills/otp-essentials && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "otp-essentials" agent skill from https://github.com/j-morgan6/elixir-phoenix-guide/tree/main/skills/otp-essentials into .opencode/skills/otp-essentials/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "otp-essentials", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
otp-essentialsA skill your agent uses when writing GenServer, Supervisor, Task, Agent, or Registry code — init/handlecontinue, call vs cast, supervision strategies, process naming.
Otp Essentials is an agent skill from j-morgan6/elixir-phoenix-guide. Use when writing GenServer, Supervisor, Task, Agent, or Registry code — init/handlecontinue, call vs cast, supervision strategies, process naming.
Its SKILL.md is about 2.6k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
The licence is MIT.
7 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit cdfddac. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are elixir).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Otp Essentials loads about 2.6k tokens when it runs. Until then it costs about 41 tokens; SKILL.md has 311 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from j-morgan6/elixir-phoenix-guide at commit cdfddac, republished under its MIT licence (© j-morgan6). 311 words, ~2,650 tokens.
.claude/skills/otp-essentials/SKILL.md (or your agent's skills folder).@impl true before GenServer/Agent callbacks (init, handle_call, handle_cast, handle_info, terminate)init/1 fast — no blocking calls, no DB queries; use handle_continue for expensive setupGenServer.call for request/response, GenServer.cast for fire-and-forget — never cast when you need a resultGenServer.call(pid, ...) directlyTask.async/Task.await with bounded timeouts — never Task.async without a corresponding Task.await or Task.yieldAlways wrap GenServer calls behind a public module API. Callers should not know they're talking to a GenServer.
# Bad — leaks GenServer implementation to callers
GenServer.call(MyApp.Cache, {:get, key})
# Good — public API hides the GenServer
defmodule MyApp.Cache do
use GenServer
# --- Public API ---
def start_link(opts) do
name = Keyword.get(opts, :name, __MODULE__)
GenServer.start_link(__MODULE__, opts, name: name)
end
def get(key, server \\ __MODULE__) do
GenServer.call(server, {:get, key})
end
def put(key, value, server \\ __MODULE__) do
GenServer.cast(server, {:put, key, value})
end
# --- Callbacks ---
@impl true
def init(_opts) do
{:ok, %{}}
end
@impl true
def handle_call({:get, key}, _from, state) do
{:reply, Map.get(state, key), state}
end
@impl true
def handle_cast({:put, key, value}, state) do
{:noreply, Map.put(state, key, value)}
end
endNever block in init/1. Use handle_continue for expensive setup.
# Bad — blocks the supervisor while loading data
@impl true
def init(opts) do
data = MyApp.Repo.all(MyApp.Item) # Blocks!
{:ok, %{items: data}}
end
# Good — returns immediately, loads data asynchronously
@impl true
def init(opts) do
{:ok, %{items: []}, {:continue, :load_data}}
end
@impl true
def handle_continue(:load_data, state) do
data = MyApp.Repo.all(MyApp.Item)
{:noreply, %{state | items: data}}
end# call — synchronous, caller waits for reply (use for reads, queries)
def get_count(server \\ __MODULE__) do
GenServer.call(server, :get_count)
end
@impl true
def handle_call(:get_count, _from, state) do
{:reply, state.count, state}
end
# cast — asynchronous, fire-and-forget (use for writes, side effects)
def increment(server \\ __MODULE__) do
GenServer.cast(server, :increment)
end
@impl true
def handle_cast(:increment, state) do
{:noreply, %{state | count: state.count + 1}}
endUse handle_info for messages not sent via call/cast — timers, monitors, PubSub, etc.
@impl true
def init(_opts) do
Process.send_after(self(), :tick, 1_000)
{:ok, %{count: 0}}
end
@impl true
def handle_info(:tick, state) do
Process.send_after(self(), :tick, 1_000)
{:noreply, %{state | count: state.count + 1}}
endHandle expected errors gracefully — reply with {:error, reason} and keep the state. Let unexpected errors crash so the supervisor restarts the process.
# Expected failure — reply with an error tuple, log it, keep serving
@impl true
def handle_call(:risky_operation, _from, state) do
case perform_operation() do
{:ok, result} ->
{:reply, {:ok, result}, update_state(state, result)}
{:error, reason} ->
Logger.error("Operation failed: #{inspect(reason)}")
{:reply, {:error, reason}, state}
end
end
# Unexpected failure — let it crash
@impl true
def handle_cast(:dangerous_work, state) do
# If this raises, the supervisor restarts the process with clean state
result = dangerous_function!()
{:noreply, Map.put(state, :result, result)}
end# one_for_one — restart only the failed child (most common)
children = [
{MyApp.Cache, []},
{MyApp.Worker, []}
]
Supervisor.start_link(children, strategy: :one_for_one)
# one_for_all — restart ALL children when one fails
# Use when children depend on each other's state
Supervisor.start_link(children, strategy: :one_for_all)
# rest_for_one — restart failed child and all children started AFTER it
# Use when later children depend on earlier ones
Supervisor.start_link(children, strategy: :rest_for_one)defmodule MyApp.Application do
use Application
@impl true
def start(_type, _args) do
children = [
MyApp.Repo,
{Phoenix.PubSub, name: MyApp.PubSub},
MyApp.Cache,
MyAppWeb.Endpoint
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
endUse when you need to start processes on demand, not at boot.
defmodule MyApp.RoomSupervisor do
use DynamicSupervisor
def start_link(init_arg) do
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
@impl true
def init(_init_arg) do
DynamicSupervisor.init(strategy: :one_for_one)
end
def start_room(room_id) do
spec = {MyApp.Room, room_id: room_id}
DynamicSupervisor.start_child(__MODULE__, spec)
end
def stop_room(pid) do
DynamicSupervisor.terminate_child(__MODULE__, pid)
end
end# Parallel fetch with bounded timeout
task1 = Task.async(fn -> fetch_user_profile(user_id) end)
task2 = Task.async(fn -> fetch_user_posts(user_id) end)
profile = Task.await(task1, 5_000)
posts = Task.await(task2, 5_000)# Process items concurrently with bounded concurrency
user_ids
|> Task.async_stream(&fetch_user/1, max_concurrency: 4, timeout: 10_000)
|> Enum.map(fn {:ok, result} -> result end)Start tasks under a Task.Supervisor for clean shutdown and observability.
Note: Task.Supervisor.start_child/2 defaults to restart: :temporary —
crashed tasks are not restarted. Pass restart: :transient if you want
restarts on abnormal exit.
# Add to your supervision tree
{Task.Supervisor, name: MyApp.TaskSupervisor}
# Start a supervised task — not restarted by default (restart: :temporary)
Task.Supervisor.start_child(MyApp.TaskSupervisor, fn ->
send_welcome_email(user)
end)
# Restart on crash — pass restart: :transient
Task.Supervisor.start_child(
MyApp.TaskSupervisor,
fn -> send_welcome_email(user) end,
restart: :transient
)Use Agent for simple state when GenServer is overkill. If you need handle_info, timeouts, or complex logic, use GenServer instead.
defmodule MyApp.Counter do
use Agent
def start_link(initial_value) do
Agent.start_link(fn -> initial_value end, name: __MODULE__)
end
def value do
Agent.get(__MODULE__, & &1)
end
def increment do
Agent.update(__MODULE__, &(&1 + 1))
end
end# In application supervision tree
{Registry, keys: :unique, name: MyApp.Registry}
# In GenServer start_link
def start_link(room_id) do
GenServer.start_link(__MODULE__, room_id,
name: {:via, Registry, {MyApp.Registry, {:room, room_id}}}
)
end
# Lookup
def get_room(room_id) do
case Registry.lookup(MyApp.Registry, {:room, room_id}) do
[{pid, _}] -> {:ok, pid}
[] -> {:error, :not_found}
end
end# OK — single global process
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
# Bad — dynamic atom creation from user input
GenServer.start_link(__MODULE__, opts, name: String.to_atom("room_#{room_id}"))# Link — bidirectional, crash propagates (use in supervisors)
Process.link(pid)
# Monitor — unidirectional, receive :DOWN message (use for observation)
ref = Process.monitor(pid)
@impl true
def handle_info({:DOWN, _ref, :process, pid, reason}, state) do
# Handle monitored process dying
{:noreply, cleanup(state, pid)}
endWhen many processes need to read the same data and writes are infrequent:
# Create table in a GenServer (owner process)
@impl true
def init(_opts) do
table = :ets.new(:my_cache, [:named_table, :set, :public, read_concurrency: true])
{:ok, %{table: table}}
end
# Any process can read
:ets.lookup(:my_cache, key)
# Only owner should write (or use :public carefully)
:ets.insert(:my_cache, {key, value})# Bad — bottleneck GenServer (all requests go through one process)
def get_user(id), do: GenServer.call(UserServer, {:get, id})
# Fix: Use ETS, a database, or partition work across multiple processes
# Bad — god process (one GenServer doing everything)
# Fix: Split into focused processes, each with one responsibility
# Bad — unmonitored Task.async
Task.async(fn -> do_work() end)
# no await or yield — caller loses track of work
# Fix: Always await, or use Task.Supervisor.start_child for fire-and-forget
# Bad — blocking the caller unnecessarily
def send_email(user) do
GenServer.call(EmailServer, {:send, user}) # Waits for email to send
end
# Fix: Use cast if caller doesn't need the result
def send_email(user) do
GenServer.cast(EmailServer, {:send, user})
end# Start GenServer in test
test "get and put values" do
start_supervised!({MyApp.Cache, name: :test_cache})
assert MyApp.Cache.get(:key, :test_cache) == nil
MyApp.Cache.put(:key, "value", :test_cache)
assert MyApp.Cache.get(:key, :test_cache) == "value"
end
# Test with Task
test "concurrent fetch" do
task = Task.async(fn -> MyApp.fetch_data() end)
assert {:ok, data} = Task.await(task, 5_000)
endSee testing-essentials skill for comprehensive testing patterns.
© j-morgan6, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/otp-essentials of j-morgan6/elixir-phoenix-guide.
Open the folder on GitHubat commit cdfddac
Otp Essentials next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Otp Essentials this skillj-morgan6/elixir-phoenix-guide | 166 | — | ~2.6k | Automated safety check: Pass | MIT | |
| Erlang Otp Behaviorsbenchflow-ai/skillsbench | 1.8k | 1 repos | ~4.5k | Automated safety check: Pass | Apache-2.0 | |
| Essentialstrycompai/comp | 2k | — | ~583 | Automated safety check: Pass | AGPL-3.0 | |
| Supervisor Review Queueanthropics/claude-for-legal | 9.6k | 3 repos | ~1.1k | Automated safety check: Pass | Apache-2.0 | |
| Elixir OTP Concurrencygeorgeguimaraes/elixir-agent-tools | 184 | — | ~1.2k | Automated safety check: Pass | Apache-2.0 | |
| Childcare EssentialsLeoYeAI/openclaw-master-skills | 2.2k | — | ~6.4k | Automated safety check: Pass | MIT |
benchflow-ai/skillsbench
A skill your agent uses when oTP behaviors including genserver for stateful processes, genstatem for state machines, supervisors for fault tolerance, genevent for event handling, and building…
trycompai/comp
Critical rules that must always be followed. An agent skill from trycompai/comp.
anthropics/claude-for-legal
Holds student work in a queue for a legal clinic professor to approve, edit-then-approve or return before anything reaches clients or courts.
georgeguimaraes/elixir-agent-tools
Guides choices among GenServer, Supervisor, Task, Registry, ETS and Broadway when designing or debugging Elixir concurrency, state and fault recovery.
LeoYeAI/openclaw-master-skills
Practical physical childcare skills for ages 0-5. An agent skill from LeoYeAI/openclaw-master-skills.
awslabs/cli-agent-orchestrator
Supervisor-side orchestration patterns for assign, handoff, and idle inbox delivery in CAO
j-morgan6/elixir-phoenix-guide
A skill your agent uses when refactoring for duplication, complexity, or dead code — includes the plugin's on-demand analysis scripts.
j-morgan6/elixir-phoenix-guide
A skill your agent uses when preparing releases or deployment config — runtime.exs vs compile-time config, release migrations, PHXHOST/PHXSERVER, assets, health checks.
j-morgan6/elixir-phoenix-guide
A skill your agent uses when a resource needs multiple changesets (registration vs update), conditional validation, field transforms, or uniqueness validation — changeset composition.
j-morgan6/elixir-phoenix-guide
A skill your agent uses when defining schemas, writing queries, or creating migrations — schema design, Repo usage, indexes, query composition.
j-morgan6/elixir-phoenix-guide
A skill your agent uses when a form or operation manages parent and child records together — castassoc/castembed, onreplace, Ecto.Multi across tables, FK cascade design.
j-morgan6/elixir-phoenix-guide
A skill your agent uses when writing or refactoring core Elixir — pattern matching, case/cond/with, pipes, {:ok, }/{:error, } contracts.
A skill your agent uses when writing GenServer, Supervisor, Task, Agent, or Registry code — init/handlecontinue, call vs cast, supervision strategies, process naming. Otp Essentials is an agent skill from j-morgan6/elixir-phoenix-guide. Use when writing GenServer, Supervisor, Task, Agent, or Registry code — init/handlecontinue, call vs cast, supervision strategies, process naming.
Otp Essentials fits situations like: writing GenServer; registry code — init/handlecontinue; supervision strategies.
Run `npx skills add j-morgan6/elixir-phoenix-guide --skill otp-essentials -a claude-code`. Or copy the skill folder (skills/otp-essentials in j-morgan6/elixir-phoenix-guide) into .claude/skills/otp-essentials in your project. Claude Code loads it when a task matches its description.
Run `npx skills add j-morgan6/elixir-phoenix-guide --skill otp-essentials -a codex`. Or copy the skill folder (skills/otp-essentials in j-morgan6/elixir-phoenix-guide) into .agents/skills/otp-essentials in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add j-morgan6/elixir-phoenix-guide --skill otp-essentials -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/otp-essentials, .gemini/skills/otp-essentials, .github/skills/otp-essentials and .opencode/skills/otp-essentials in your project.
SKILL.md names no scripts, command-line tools or credentials: Otp Essentials is instructions for the agent only.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Otp Essentials is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 2.6k tokens (SKILL.md is roughly 11k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Otp Essentials: Erlang Otp Behaviors (benchflow-ai/skillsbench, 1.8k stars), Essentials (trycompai/comp, 2k stars), Supervisor Review Queue (anthropics/claude-for-legal, 9.6k stars) and Elixir OTP Concurrency (georgeguimaraes/elixir-agent-tools, 184 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
j-morgan6 (a GitHub user) maintains it in j-morgan6/elixir-phoenix-guide, which has 166 GitHub stars. The repository holds 19 skills in this directory. The repository was last updated on July 6, 2026.
Source: j-morgan6/elixir-phoenix-guide on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.