skill/elixir-phoenix-framework/SKILL.md
# Elixir Phoenix Framework Skill ## Overview Comprehensive Phoenix framework patterns focusing on generators, LiveView, context design, and version-specific best practices. Emphasizes generator-first development approach. ## Phoenix Generator Reference Guide ### Core Generators ```bash # Project scaffolding mix phx.new app_name # New Phoenix application mix phx.new app_name --umbrella # Umbrella application mix phx.new app_name --no-ecto # Without Ecto data
npx skillsauth add vircung/opencode-config skill/elixir-phoenix-frameworkInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Comprehensive Phoenix framework patterns focusing on generators, LiveView, context design, and version-specific best practices. Emphasizes generator-first development approach.
# Project scaffolding
mix phx.new app_name # New Phoenix application
mix phx.new app_name --umbrella # Umbrella application
mix phx.new app_name --no-ecto # Without Ecto database
mix phx.new app_name --live # With LiveView (Phoenix 1.6+)
# Context and schema generation
mix phx.gen.context Accounts User users name:string email:string:unique
mix phx.gen.schema Blog.Post posts title:string content:text published_at:datetime
# Full resource generation
mix phx.gen.html Accounts User users name:string email:string
mix phx.gen.json Api User users name:string email:string
mix phx.gen.live Blog Post posts title:string content:text --no-context
# Authentication (Phoenix 1.7+)
mix phx.gen.auth Accounts User users
# LiveView components
mix phx.gen.live Catalog Product products name:string price:decimal
mix phx.gen.live.modal Blog Post posts title:string # Modal forms
mix phx.gen.live.table Orders Order orders status:string # Data tables
# Custom LiveView
mix phx.gen.live Dashboard Stats stats name:string value:integer --no-context
# Migrations
mix ecto.gen.migration add_users_table
mix ecto.gen.migration add_email_index_to_users
# Migration patterns
mix ecto.gen.migration create_join_table_users_roles
mix ecto.gen.migration add_timestamps_to_existing_table
# Start with context design
mix phx.gen.context Blog Post posts title:string content:text status:string
# Add relationships iteratively
mix phx.gen.context Blog Comment comments content:text post_id:references:posts
mix ecto.gen.migration add_user_id_to_posts
# Generate views after contexts are stable
mix phx.gen.html Blog Post posts title:string content:text status:string
# Add LiveView for interactive features
mix phx.gen.live Blog Post posts title:string content:text --no-context
# Modify generated templates in priv/templates/phx.gen.*
# Common customizations:
# - Add authentication checks to controllers
# - Include form validation feedback
# - Add pagination to index views
# - Include search functionality
# ✅ Proper LiveView state structure
defmodule AppWeb.PostLive.Index do
use AppWeb, :live_view
def mount(_params, _session, socket) do
if connected?(socket), do: Blog.subscribe()
{:ok,
socket
|> assign(:posts, list_posts())
|> assign(:loading, false)
|> assign(:page_title, "Posts")}
end
defp list_posts do
Blog.list_posts() |> Blog.preload_authors()
end
end
# ❌ Avoid: Heavy computation in assigns
def mount(_params, _session, socket) do
posts = Enum.map(Blog.list_posts(), &expensive_computation/1) # Too slow
{:ok, assign(socket, posts: posts)}
end
def render(assigns) do
~H"""
<.simple_form for={@form} phx-submit="save" phx-change="validate">
<.input field={@form[:title]} type="text" label="Title" required />
<.input field={@form[:content]} type="textarea" label="Content" rows="10" />
<:actions>
<.button phx-disable-with="Saving...">Save Post</.button>
</:actions>
</.simple_form>
"""
end
def handle_event("validate", %{"post" => post_params}, socket) do
changeset = Blog.change_post(%Post{}, post_params)
{:noreply, assign_form(socket, changeset)}
end
# Subscription pattern for real-time updates
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(App.PubSub, "posts")
Phoenix.PubSub.subscribe(App.PubSub, "user:#{socket.assigns.current_user.id}")
end
{:ok, socket}
end
def handle_info({:post_created, post}, socket) do
{:noreply, update(socket, :posts, &[post | &1])}
end
# In context module
def create_post(attrs) do
# ... create post logic
Phoenix.PubSub.broadcast(App.PubSub, "posts", {:post_created, post})
{:ok, post}
end
# ✅ Well-designed context boundaries
defmodule App.Accounts do
# User management, authentication, profiles
def get_user!(id), do: # ...
def create_user(attrs), do: # ...
def authenticate_user(email, password), do: # ...
end
defmodule App.Blog do
# Content management
def list_posts(), do: # ...
def create_post(user, attrs), do: # ...
def publish_post(post), do: # ...
end
defmodule App.Billing do
# Payment processing, subscriptions
def create_subscription(user, plan), do: # ...
def process_payment(subscription), do: # ...
end
# ✅ Proper cross-context interaction
defmodule App.Blog do
alias App.Accounts
def create_post_for_user(user_id, attrs) do
with user when not is_nil(user) <- Accounts.get_user(user_id),
{:ok, post} <- create_post(Map.put(attrs, :user_id, user.id)) do
{:ok, post}
else
nil -> {:error, :user_not_found}
error -> error
end
end
end
# ❌ Avoid: Direct schema access across contexts
def create_post(attrs) do
user = Repo.get(Accounts.User, attrs.user_id) # Direct access - bad
# ...
end
--live flag in new projects# New function component syntax
def my_component(assigns) do
~H"""
<div class={@class}>
<%= @inner_block %>
</div>
"""
end
# Verified routes (compile-time checking)
~p"/posts/#{@post.id}" # Instead of Routes.post_path()
# Built-in authentication generator
mix phx.gen.auth Accounts User users
# Phoenix upgrade workflow
mix phx.gen.release --upgrade # For existing apps
mix deps.update phoenix # Update dependencies
mix ecto.migrate # Run pending migrations
# ✅ Efficient preloading
def list_posts_with_authors do
from(p in Post, preload: [:author, comments: :author])
|> Repo.all()
end
# ✅ Pagination with streaming
def list_posts_paginated(page \\ 1, per_page \\ 20) do
Post
|> order_by([p], desc: p.inserted_at)
|> Repo.paginate(page: page, page_size: per_page)
end
# ✅ Efficient LiveView updates
def handle_event("search", %{"query" => query}, socket) do
# Debounce searches
Process.send_after(self(), {:perform_search, query}, 300)
{:noreply, assign(socket, :search_query, query)}
end
def handle_info({:perform_search, query}, socket) do
if socket.assigns.search_query == query do
results = Search.perform(query)
{:noreply, assign(socket, :search_results, results)}
else
{:noreply, socket}
end
end
# Phoenix 1.7+ auth patterns
defmodule AppWeb.UserAuth do
def require_authenticated_user(conn, _opts) do
if conn.assigns[:current_user] do
conn
else
conn
|> put_flash(:error, "You must log in to access this page.")
|> redirect(to: ~p"/users/log_in")
|> halt()
end
end
end
# In router
pipeline :require_auth do
plug AppWeb.UserAuth, :require_authenticated_user
end
# In endpoint.ex
plug Plug.CSRFProtection
plug Plug.SecureHeaders, [
{"content-security-policy", "default-src 'self'"},
{"x-frame-options", "DENY"},
{"x-content-type-options", "nosniff"}
]
defmodule AppWeb.PostLive.IndexTest do
use AppWeb.ConnCase
import Phoenix.LiveViewTest
test "displays posts", %{conn: conn} do
post = insert(:post)
{:ok, _index_live, html} = live(conn, ~p"/posts")
assert html =~ post.title
assert has_element?(index_live, "#post-#{post.id}")
end
test "creates post in real time", %{conn: conn} do
{:ok, index_live, _html} = live(conn, ~p"/posts")
{:ok, _new_live, _html} =
index_live
|> element("a", "New Post")
|> render_click()
|> follow_redirect(conn, ~p"/posts/new")
end
end
# Custom error handling
defmodule AppWeb.ErrorView do
use AppWeb, :view
def render("404.html", _assigns) do
"Page not found"
end
def render("500.html", _assigns) do
"Internal server error"
end
# JSON API errors
def render("error.json", %{changeset: changeset}) do
%{errors: translate_errors(changeset)}
end
end
elixir-architecture patterns for proper boundary designelixir-ecto patterns for schema and query optimizationelixir-review security and performance guidelineselixir-otp for background job patterns with Phoenixpriv/templates/
├── phx.gen.html/
│ ├── controller.ex
│ ├── view.ex
│ └── templates/
├── phx.gen.live/
│ ├── index.ex
│ ├── show.ex
│ └── form_component.ex
└── phx.gen.context/
├── context.ex
└── schema.ex
# Add authentication to generated controllers
def index(conn, _params) do
user = conn.assigns.current_user
<%= schema.plural %> = <%= context.alias %>.list_<%= schema.plural %>(user)
render(conn, "index.html", <%= schema.plural %>: <%= schema.plural %>)
end
# Add search to LiveView index
def handle_event("search", %{"search" => %{"query" => query}}, socket) do
<%= schema.plural %> = <%= context.alias %>.search_<%= schema.plural %>(query)
{:noreply, assign(socket, :<%= schema.plural %>, <%= schema.plural %>)}
end
# config/runtime.exs for Phoenix 1.7+
if System.get_env("PHX_SERVER") do
config :app, AppWeb.Endpoint, server: true
end
if config_env() == :prod do
config :app, App.Repo,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
end
Use this skill to build Phoenix applications efficiently using generators, implement LiveView patterns correctly, and follow Phoenix conventions for scalable web applications.
development
Python code security analysis, performance optimization, and maintainability assessment
development
Modern Python coding standards, best practices, testing patterns, and implementation guidelines
development
Python system design patterns, project structure, and scalable architecture guidelines
development
# Elixir Code Review Skill ## Overview Comprehensive code review patterns for Elixir applications focusing on BEAM VM performance, security, concurrency safety, and code quality metrics. ## BEAM VM Performance Analysis ### Process Management - **Process Count:** Monitor with `:observer.start()` - avoid creating >1M processes without justification - **Process Isolation:** Ensure processes don't share mutable state - use message passing exclusively - **Process Linking:** Review supervisor trees