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).
91
91%
Does it follow best practices?
Impact
91%
1.37xAverage score across 56 eval scenarios
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
endDemonstrates
OptionParserwith required flags, error reporting, dry-run support, and before/after count verification.
defmodule 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 → prompt the operator for confirmation → Repo.delete_all/1 only if confirmed |
All follow the same skeleton: parse opts → app.start → transact → report. A task that
deletes data must default to --dry-run reporting and require an explicit --force (or
interactive confirmation) flag before calling Repo.delete_all/1 — never delete
unconditionally based on the task's own judgment.
| ❌ Don't | ✅ Do |
|---|---|
Access Repo before the app is started | Call Mix.Task.run("app.start") first |
Repo.insert_all / delete_all without a transaction | Wrap data mutations in Repo.transaction/1 |
Override a built-in task like mix test | Namespace custom tasks under Mix.Tasks.MyApp.* |
Mix.Tasks.MyApp.SeedData in a mismatched file | Match the path: lib/mix/tasks/my_app.seed_data.ex |
Omit @shortdoc | Add @shortdoc so the task appears in mix help |
Ignore the OptionParser errors list | Check errors and Mix.raise/1 on invalid args |
| Hand-roll CRUD boilerplate | Scaffold with mix phx.gen.live / phx.gen.context / phx.gen.json |
| Delete records without operator confirmation | Require --force or an interactive y/N prompt before Repo.delete_all/1 |
| Predecessor | This Skill | Successor |
|---|---|---|
| elixir-essentials | mix-tasks-generators | ecto-essentials |
| None (standalone) | mix-tasks-generators | testing-essentials |
Companion skills: ecto-essentials, testing-essentials, oban-essentials.
.tessl-plugin
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
scenario-27
scenario-28
scenario-29
scenario-30
scenario-31
scenario-32
scenario-33
scenario-34
scenario-35
scenario-36
scenario-37
scenario-38
scenario-39
scenario-40
scenario-41
scenario-42
scenario-43
scenario-44
scenario-45
scenario-46
scenario-47
scenario-48
scenario-49
scenario-50
scenario-51
scenario-52
scenario-53
scenario-54
scenario-55
scenario-56
skills
frameworks
ash-framework
infrastructure
orchestration
elixir-skill-router
personas
phoenix
quality
security
security-essentials
tooling
mix-tasks-generators