Curated library of 38 atomic skills, 7 personas, and 1 orchestrator for Elixir and Phoenix development. Organized by category: fundamentals, phoenix, database, testing, auth, infrastructure, quality, security, integrations, tooling, frameworks, personas, and orchestration. Covers core Elixir patterns, Phoenix LiveView, Ecto, OTP, Oban, testing, security, deployment, real-time, and modern tooling (Req, Swoosh, Cachex, Broadway, Ash).
73
91%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Mix.Task.run("app.start") first — tasks that access the database or Repo need the app startedpreferred_cli_env in mix.exs — set environment for custom tasks (dev/test/prod)Repo.insert_all, Repo.delete_all in Repo.transaction()Mix.Project.in_project/4 — ensure tasks work correctly in isolationMix.Tasks.Namespace.TaskName naming — file path must match: lib/mix/tasks/namespace.task_name.exFollow this sequence when creating a custom Mix task:
lib/mix/tasks/<namespace>.<task_name>.exdefmodule Mix.Tasks.MyApp.TaskName do use Mix.Task@shortdoc — one-line description for mix helprun/1 — parse args with OptionParser.parse/2 if neededMix.Task.run("app.start") if using Repopreferred_cli_env if neededMix.Project.in_project/4 test helpermix help | grep task_name to confirm registration# JSON context and schema (full CRUD with tests)
mix phx.gen.json Accounts User users email:string name:string --web API
# LiveView CRUD with Enum field
mix phx.gen.live Blog Post posts title:string body:text status:enum:draft:published:archived
# LiveView for existing table
mix phx.gen.live Blog Post posts --table existing_posts
# Context (logic layer only)
mix phx.gen.context Accounts User users email:string password_hash:string
# Channel
mix phx.gen.channel Room
# HTML (no LiveView)
mix phx.gen.html Blog Post posts title:string body:text
# Authentication system (accounts context, user schema, session plumbing)
mix phx.gen.auth Accounts User users --web Admin
# Context with existing schema
mix phx.gen.context Blog Post posts --table posts --app MyApp# lib/mix/tasks/my_app.seed_data.ex
defmodule Mix.Tasks.MyApp.SeedData do
use Mix.Task
@shortdoc "Seeds the database with sample data"
@impl Mix.Task
def run(_args) do
Mix.Task.run("app.start")
case MyApp.Repo.transaction(fn -> MyApp.Seeds.run() end) do
{:ok, _} ->
count = MyApp.Repo.aggregate(MyApp.User, :count)
Mix.shell().info("Data seeded successfully! (#{count} users in DB)")
{:error, reason} ->
Mix.shell().error("Seeding failed, transaction rolled back: #{inspect(reason)}")
Mix.raise("Seed failed")
end
end
enddefmodule Mix.Tasks.MyApp.ImportUsers do
use Mix.Task
@shortdoc "Imports users from a CSV file"
@impl Mix.Task
def run(args) do
{opts, _rest, errors} =
OptionParser.parse(args,
strict: [file: :string, dry_run: :boolean],
aliases: [f: :file, d: :dry_run]
)
if Keyword.get(opts, :file) == nil do
Mix.shell().error("Missing required --file argument")
Mix.shell().info("\nUsage: mix my_app.import_users --file <path>")
Mix.raise("Invalid arguments")
end
if errors != [] do
Enum.each(errors, fn {key, value} -> Mix.shell().error("Unknown option: #{key} #{value}") end)
Mix.raise("Invalid arguments")
end
Mix.Task.run("app.start")
file = Keyword.get(opts, :file)
dry_run? = Keyword.get(opts, :dry_run, false)
if dry_run?, do: Mix.shell().info("Dry run mode - no changes will be made")
before_count = MyApp.Repo.aggregate(MyApp.User, :count)
case MyApp.Repo.transaction(fn ->
MyApp.UserImporter.import_from_csv(file, dry_run: dry_run?)
end) do
{:ok, {:ok, count}} ->
after_count = MyApp.Repo.aggregate(MyApp.User, :count)
Mix.shell().info("Imported #{count} users (total: #{after_count}, was: #{before_count})")
{:ok, {:error, reason}} ->
Mix.shell().error("Import validation failed: #{reason}")
Mix.raise("Import failed")
{:error, reason} ->
Mix.shell().error("Import failed, transaction rolled back: #{inspect(reason)}")
Mix.raise("Import failed")
end
end
end
# Usage: mix my_app.import_users --file=users.csv --dry-rundefmodule Mix.Tasks.MyApp.Migrate do
use Mix.Task
@shortdoc "Runs database migrations"
@impl Mix.Task
def run(["up"]), do: Mix.Task.run("ecto.migrate")
def run(["down"]), do: Mix.Task.run("ecto.rollback")
def run(["status"]), do: Mix.Task.run("ecto.migrations")
def run(_) do
Mix.shell().info("""
Usage:
mix my_app.migrate up - Run migrations
mix my_app.migrate down - Rollback last migration
mix my_app.migrate status - Show migration status
""")
end
end# lib/mix/tasks/my_app.gen.service.ex
defmodule Mix.Tasks.MyApp.Gen.Service do
use Mix.Task
use Mix.Generator
@shortdoc "Generates a service module"
@impl Mix.Task
def run([name]) do
module_name = Macro.camelize(name)
file_path = "lib/my_app/services/#{name}.ex"
create_file(file_path, service_template(module_name: module_name))
Mix.shell().info("Created service: #{file_path}")
end
embed_template(:service, """
defmodule MyApp.Services.<%= @module_name %> do
@moduledoc \"\"\"
Service module for <%= @module_name %> operations.
\"\"\"
def call(params) do
{:ok, params}
end
end
""")
end
# Usage: mix my_app.gen.service send_email
# Creates: lib/my_app/services/send_email.ex# mix.exs
defp aliases do
[
setup: ["deps.get", "ecto.setup"],
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
"assets.deploy": ["esbuild default --minify", "phx.digest"]
]
enddef project do
[
app: :my_app,
version: "0.1.0",
elixir: "~> 1.15",
start_permanent: Mix.env() == :prod,
deps: deps(),
aliases: aliases(),
preferred_cli_env: [
"my_app.seed_data": :dev,
"my_app.import_users": :dev
]
]
enddefmodule Mix.Tasks.MyApp.SeedDataTest do
use ExUnit.Case
setup do
MyApp.Repo.delete_all(MyApp.User)
:ok
end
test "seeds data successfully" do
Mix.Project.in_project(:my_app, ".", fn _ ->
Mix.Tasks.MyApp.SeedData.run([])
assert MyApp.Repo.aggregate(MyApp.User, :count) > 0
end)
end
test "seeds expected count" do
Mix.Project.in_project(:my_app, ".", fn _ ->
Mix.Tasks.MyApp.SeedData.run([])
assert MyApp.Repo.aggregate(MyApp.User, :count) == 10
end)
end
end| Pattern | Key Steps |
|---|---|
Database Reset (my_app.reset) | Chain ecto.drop → ecto.create → ecto.migrate → seed via Mix.Task.run/1 |
Health Check (my_app.health_check) | app.start → run named checks (DB, cache, HTTP) → report pass/fail → Mix.raise/1 on any failure |
Cleanup (my_app.cleanup) | Parse --dry-run flag → query expired records → report count → conditionally Repo.delete_all/1 |
All follow the same skeleton: parse opts → app.start → transact → report.
Mix.shell().info("Starting task...")
Mix.shell().info("✓ Success")
Mix.shell().error("✗ Failed")
# Progress reporting for long operations
records
|> Enum.with_index()
|> Enum.each(fn {record, index} ->
process_record(record)
if rem(index, 100) == 0, do: Mix.shell().info(" Processed #{index + 1}/#{total} records")
end)