elixir-essentials
Use when writing or refactoring core Elixir — pattern matching, case/cond/with, pipes, {:ok, _}/{:error, _} contracts. Baseline style for any .ex/.exs change not covered by a more specific skill.
Works with
Agent Skills format with YAML frontmatter. Claude Code reads it as-is.
---
name: "elixir-essentials"
description: "Use when writing or refactoring core Elixir — pattern matching, case/cond/with, pipes, {:ok, _}/{:error, _} contracts. Baseline style for any .ex/.exs change not covered by a more specific skill."
license: "MIT"
---
# Elixir Essentials
## RULES — Follow these with no exceptions
1. **Use pattern matching over if/else** for control flow and data extraction
2. **Add @impl true** before every callback function (mount, handle_event, handle_info, etc.)
3. **Return {: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. **Predicate functions end with `?`**, dangerous functions end with `!`
8. **Let it crash** — don't write defensive code for impossible states
---
## Pattern Matching
Pattern matching is the primary control flow mechanism in Elixir. Prefer it over conditional statements.
### Prefer Pattern Matching Over if/else
**Bad:**
```elixir
def process(result) do
if result.status == :ok do
result.data
else
nil
end
end
```
**Good:**
```elixir
def process(%{status: :ok, data: data}), do: data
def process(_), do: nil
```
### Use Case for Multiple Patterns
**Bad:**
```elixir
# Nested if inside else — hard to read and not how Elixir expresses multi-way branching
def handle_response(response) do
if response.status == 200 do
{:ok, response.body}
else
if response.status == 404 do
{:error, :not_found}
else
{:error, :unknown}
end
end
end
```
**Good:**
```elixir
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
Use the pipe operator `|>` to chain function calls for improved readability.
### Basic Piping
**Bad:**
```elixir
String.upcase(String.trim(user_input))
```
**Good:**
```elixir
user_input
|> String.trim()
|> String.upcase()
```
### Pipe into Function Heads
**Bad:**
```elixir
def process_user(user) do
validated = validate_user(user)
transformed = transform_user(validated)
save_user(transformed)
end
```
**Good:**
```elixir
def process_user(user) do
user
|> validate_user()
|> transform_user()
|> save_user()
end
```
## With Statement
Use `with` for sequential operations that can fail.
**Bad:**
```elixir
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:**
```elixir
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
Handle specific errors in the else block.
```elixir
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
Use guards for simple type and value checks in function heads.
```elixir
def calculate(x) when is_integer(x) and x > 0 do
x * 2
end
def calculate(_), do: {:error, :invalid_input}
```
## List Comprehensions
Use `for` comprehensions for complex transformations and filtering.
**Bad (multiple passes):**
```elixir
list
|> Enum.map(&transform/1)
|> Enum.filter(&valid?/1)
|> Enum.map(&format/1)
```
**Good (single pass):**
```elixir
for item <- list,
transformed = transform(item),
valid?(transformed) do
format(transformed)
end
```
## Naming Conventions
- Module names: `PascalCase`
- Function names: `snake_case`
- Variables: `snake_case`
- Atoms: `:snake_case`
- Predicate functions end with `?`: `valid?`, `empty?`
- Dangerous functions end with `!`: `save!`, `update!`
## Tagged Tuples for Error Handling
The idiomatic way to handle success and failure in Elixir.
```elixir
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
```
## Case Statements
Pattern match on results.
```elixir
def process_upload(file) do
case save_file(file) do
{:ok, path} ->
Logger.info("File saved to #{path}")
create_record(path)
{:error, :invalid_format} ->
{:error, "File format not supported"}
{:error, reason} ->
Logger.error("Upload failed: #{inspect(reason)}")
{:error, "Upload failed"}
end
end
```
## Bang Functions
Functions ending with `!` raise errors instead of returning tuples.
```elixir
# Returns {:ok, user} or {:error, changeset}
def create_user(attrs) do
%User{}
|> User.changeset(attrs)
|> Repo.insert()
end
# Returns user or raises
def create_user!(attrs) do
%User{}
|> User.changeset(attrs)
|> Repo.insert!()
end
# Usage
try do
user = create_user!(invalid_attrs)
IO.puts("Created #{user.name}")
rescue
e in Ecto.InvalidChangesetError ->
IO.puts("Failed: #{inspect(e)}")
end
```
## Try/Rescue
Catch exceptions when needed (use sparingly).
```elixir
def parse_json(string) do
try do
{:ok, Jason.decode!(string)}
rescue
Jason.DecodeError -> {:error, :invalid_json}
end
end
```
> Processes, GenServers, supervision: see the **otp-essentials** skill.
## Validation Errors
Return clear, actionable error messages.
```elixir
def validate_image_upload(file) do
with :ok <- validate_file_type(file),
:ok <- validate_file_size(file),
:ok <- validate_dimensions(file) do
{:ok, file}
else
{:error, :invalid_type} ->
{:error, "Only JPEG, PNG, and GIF files are allowed"}
{:error, :too_large} ->
{:error, "File must be less than 10MB"}
{:error, :invalid_dimensions} ->
{:error, "Image must be at least 100x100 pixels"}
end
end
```
## Changeset Errors
Extract and format Ecto changeset errors.
```elixir
def changeset_errors(changeset) do
Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", to_string(value))
end)
end)
end
# Usage
case create_user(attrs) do
{:ok, user} -> {:ok, user}
{:error, changeset} ->
errors = changeset_errors(changeset)
{:error, errors}
end
```
## Early Returns
Use pattern matching in function heads for early returns.
```elixir
def process_data(nil), do: {:error, :no_data}
def process_data([]), do: {:error, :empty_list}
def process_data(data) when is_list(data) do
# Process the list
{:ok, Enum.map(data, &transform/1)}
end
```
## Avoid Defensive Programming
Don't check for things that can't happen. Let it crash.
**Bad (defensive):**
```elixir
def get_username(user) do
if user && user.name do
user.name
else
"Unknown"
end
end
```
**Good (trust your types):**
```elixir
def get_username(%User{name: name}), do: name
```
If the user is nil or doesn't have a name, it's a bug that should crash and be fixed.
## Documentation
Use `@doc` for public functions and `@moduledoc` for modules.
```elixir
defmodule MyModule do
@moduledoc """
This module handles user operations.
"""
@doc """
Fetches a user by ID.
Returns `{:ok, user}` or `{:error, :not_found}`.
"""
def fetch_user(id), do: # ...
end
```
## Immutability
All data structures are immutable. Functions return new values rather than modifying in place.
```elixir
# Always returns a new list
list = [1, 2, 3]
new_list = [0 | list] # [0, 1, 2, 3]
# list is still [1, 2, 3]
```
## Testing
When writing test files for Elixir modules, invoke `elixir-phoenix-guide:testing-essentials` before writing any `_test.exs` file.
## Anonymous Functions
Use the capture operator `&` for concise anonymous functions.
**Verbose:**
```elixir
Enum.map(list, fn x -> x * 2 end)
```
**Concise:**
```elixir
Enum.map(list, &(&1 * 2))
```
**Named function capture:**
```elixir
Enum.map(users, &User.format/1)
```More Refactoring skills
vercel-react-best-practices
vercel-labs/agent-skills
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
analyze-project
lllllllama/rigorpilot-skills
Rigor Analyze / Rigor Audit read-only skill for deep learning research repositories. Use when the user wants to read and understand a repository, inspect model structure and training or inference entrypoints, review configs and insertion points, or flag suspicious implementation patterns without modifying code or running heavy jobs. Do not use for active command execution, broad refactoring, speculative code adaptation, or automatic bug fixing.
request-refactor-plan
mattpocock/skills
Create a detailed refactor plan with tiny commits via user interview, then file it as a GitHub issue. Use when user wants to plan a refactor, create a refactoring RFC, or break a refactor into safe incremental steps.

