CtrlK
BlogDocsLog inGet started
Tessl Logo

igmarin/elixir-phoenix-skills

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

1.37x
Quality

91%

Does it follow best practices?

Impact

91%

1.37x

Average score across 56 eval scenarios

SecuritybySnyk

Advisory

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

SKILL.mdskills/fundamentals/elixir-essentials/

name:
elixir-essentials
type:
atomic
tags:
atomic
license:
MIT
description:
MANDATORY for ALL Elixir code changes. Invoke before writing any .ex or .exs file. Covers pattern matching, pipe operator, with statements, error handling with tagged tuples, guards, list comprehensions, naming conventions, and the "let it crash" philosophy. Trigger words: elixir, pattern matching, pipe, with, error handling, tagged tuples, guards.

Elixir Essentials

Use this skill before writing ANY .ex or .exs file.

RULES — Follow these with no exceptions

  1. Use pattern matching over if/else for control flow and data extraction
  2. Use @impl true before every callback function (mount, handle_event, handle_info, etc.)
  3. Produce {:ok, result} | {:error, reason} tuples for fallible operations
  4. Use with for 2+ sequential fallible operations instead of nested case
  5. Use the pipe operator for 2+ chained transformations
  6. Never nest if/else statements — use case, cond, or multi-clause functions
  7. Use ? suffix for predicate functions, ! suffix for dangerous functions
  8. Never write defensive code for impossible states — let it crash
  9. Use @doc and @moduledoc for all public APIs
  10. Prefer immutability — never mutate data in place
  11. Don't use String.to_atom/1 on user input (atom table exhaustion)
  12. Prefer for comprehensions before chaining 3+ Enum operations

Pattern Matching

Good — prefer multi-clause functions with pattern matching over if/else:

def handle_response(%{status: 200, body: body}), do: {:ok, body}
def handle_response(%{status: 404}), do: {:error, :not_found}
def handle_response(_), do: {:error, :unknown}

Pipe Operator

def process_user(user) do
  user
  |> validate_user()
  |> transform_user()
  |> save_user()
end

With Statement

Bad (nested case):

def create_post(params) do
  case validate_params(params) do
    {:ok, valid_params} ->
      case create_changeset(valid_params) do
        {:ok, changeset} ->
          Repo.insert(changeset)
        error -> error
      end
    error -> error
  end
end

Good (with):

def create_post(params) do
  with {:ok, valid_params} <- validate_params(params),
       {:ok, changeset} <- create_changeset(valid_params),
       {:ok, post} <- Repo.insert(changeset) do
    {:ok, post}
  end
end

With Statement — Inline Error Handling

def transfer_money(from_id, to_id, amount) do
  with {:ok, from_account} <- get_account(from_id),
       {:ok, to_account} <- get_account(to_id),
       :ok <- validate_balance(from_account, amount),
       {:ok, _} <- debit(from_account, amount),
       {:ok, _} <- credit(to_account, amount) do
    {:ok, :transfer_complete}
  else
    {:error, :insufficient_funds} ->
      {:error, "Not enough money in account"}

    {:error, :not_found} ->
      {:error, "Account not found"}

    error ->
      {:error, "Transfer failed: #{inspect(error)}"}
  end
end

Guards

def calculate(x) when is_integer(x) and x > 0 do
  x * 2
end

def calculate(_), do: {:error, :invalid_input}

List Comprehensions

Bad (multiple passes):

list
|> Enum.map(&transform/1)
|> Enum.filter(&valid?/1)
|> Enum.map(&format/1)

Good (single pass):

for item <- list,
    transformed = transform(item),
    valid?(transformed) do
  format(transformed)
end

Naming Conventions

ElementConventionExample
Module namesPascalCaseMyApp.Accounts.User
Function namessnake_casecreate_user/1
Variablessnake_caseuser_name
Atoms:snake_case:not_found
Predicate functionsend with ?valid?, empty?
Dangerous functionsend with !save!, update!

Tagged Tuples for Error Handling

def fetch_user(id) do
  case Repo.get(User, id) do
    nil -> {:error, :not_found}
    user -> {:ok, user}
  end
end

# Usage
case fetch_user(123) do
  {:ok, user} -> IO.puts("Found: #{user.name}")
  {:error, :not_found} -> IO.puts("User not found")
end

Bang Functions

Use ! suffix for functions that raise on failure. Prefer the non-bang variant in application logic; use bang in tests or when failure is truly unrecoverable.

Early Returns

def process_data(nil), do: {:error, :no_data}
def process_data([]), do: {:error, :empty_list}
def process_data(data) when is_list(data) do
  {:ok, Enum.map(data, &transform/1)}
end

Avoid Defensive Programming

Good (trust your types):

def get_username(%User{name: name}), do: name

Common Pitfalls

❌ Don't✅ Do
Nest case/if for sequential fallible stepsChain them with a single with
Write defensive code for impossible statesTrust your types and let it crash
Call String.to_atom/1 on user inputUse String.to_existing_atom/1 or keep strings (atom table is finite)
Return bare values or raise for expected failuresReturn {:ok, result} / {:error, reason} tuples
Chain 3+ Enum.map/filter passes over a listUse a single for comprehension
Mutate data in placeReturn new immutable values from each transformation
Skip @impl true on callbacksAnnotate every callback with @impl true

Integration

PredecessorThis SkillSuccessor
None (always first)elixir-essentialsotp-essentials
None (always first)elixir-essentialstesting-essentials

Companion skills:

  • otp-essentials — processes, GenServer, and supervision
  • typespec-dialyzer — static typing and Dialyzer analysis
  • testing-essentials — ExUnit and TDD workflow
  • code-quality — Credo/Dialyzer/format loop before PR

When Not to Use

  • Phoenix/LiveView application context — use the relevant Phoenix/LiveView skill instead
  • OTP patterns (GenServer, Supervisor, Agent) — use otp-essentials instead
  • Database operations — use ecto-essentials instead
  • Type specifications or Dialyzer integration — use typespec-dialyzer instead

skills

fundamentals

elixir-essentials

.mcp.json

README.md

tile.json