Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Ash Framework — resources, actions, policies, aggregates, calculations, AshPhoenix.Form, LiveView, migrations. Use when generating resources via mix ash.codegen, editing changes, checks, types, validations, or domain code interfaces.
effort
medium
user-invocable
false
Ash Framework Reference
Reference for Ash Framework in Phoenix/LiveView projects.
Ash complements Phoenix/Ecto — LiveView, security, and OTP Iron Laws still apply.
Only data access patterns shift toward Ash actions and domain code interfaces.
Iron Laws
USE DOMAIN CODE INTERFACES — Never call Ash.create/Ash.read directly in LiveViews or Controllers; use domain code interfaces: MyApp.Accounts.register_user() not Ash.create(User, attrs)
SET ACTOR/SCOPE AT QUERY PREP, NOT EXECUTION — Pass actor: or scope: to
for_read/for_create/for_action (prep), NOT to Ash.read!/Ash.create! (execution);
execution-level actor bypasses row-level policy evaluation. If project uses Ash.Scope,
pass scope: consistently instead of bare actor: — do not mix styles
GENERATORS FIRST — Before writing Ash code manually, run mix ash.gen.resource or mix ash.gen.domain with ; check for options
--yes
mix help ash.gen.<task>
CODEGEN AFTER RESOURCE CHANGES — Always run mix ash.codegen after modifying resources; this generates migrations from resource snapshots — never write AshPostgres migrations by hand
ACTIONS OVER FUNCTIONS — Put business logic in named actions, not domain functions; expose via code interfaces defined on the domain
NEVER EDIT RESOURCE SNAPSHOTS — priv/resource_snapshots/ is owned exclusively by mix ash.codegen; manual edits corrupt migration tracking
NO DIRECT Repo.* IN ASH PROJECTS — Repo.all/get/insert bypass Ash policies and notifications; use domain code interfaces. Any Repo call in an Ash project is an escape hatch and must be documented
Quick Reference
Domain Code Interface Pattern
# Domain definition
defmodule MyApp.Accounts do
use Ash.Domain
resources do
resource MyApp.Accounts.User do
define :register_user, action: :create, args: [:email, :password]
define :get_user_by_email, action: :read, get_by: [:email]
end
end
end
# In LiveView/Controller — always via domain, never Ash.create directly
{:ok, user} = MyApp.Accounts.register_user(email, password, actor: nil)
user = MyApp.Accounts.get_user_by_email!(email, actor: current_user)
Authorization — Actor/Scope at Query Prep
# CORRECT — actor at query prep, policies evaluated per-row
MyApp.Post
|> Ash.Query.for_read(:list_published, %{}, actor: current_user)
|> Ash.read!()
# CORRECT with Ash.Scope (carries actor + tenant + context; use if project adopts it)
MyApp.Post
|> Ash.Query.for_read(:list_published, %{}, scope: scope)
|> Ash.read!()
# WRONG — actor at execution bypasses row-level policy evaluation
MyApp.Post
|> Ash.Query.for_read(:list_published)
|> Ash.read!(actor: current_user)
Ash.Scope — When the Project Uses It
Ash.Scope bundles actor + tenant + context into a single struct passed through actions.
Implement Ash.Scope.ToOpts on a project-defined scope struct:
Detection: if the project has a Scope module implementing Ash.Scope.ToOpts, use
scope: everywhere instead of bare actor:. Do NOT mix the two styles in the same codebase.
See mix usage_rules.docs Ash.Scope for full protocol spec.