Model your domain,
derive the rest
Discover Ash Features
Resources & Actions
Step 1 of 9
Resources & Actions are the core abstraction in Ash. Actions are fully typed and introspectable (your application can examine them at runtime). This means extensions can automatically understand and build on top of them.
defmodule MyApp.Blog.Post do
use Ash.Resource
actions do
action :reading_time, :integer do
argument :content, :string,
allow_nil?: false
run fn input, _ ->
words =
input.arguments.content
|> String.split()
|> length()
{:ok, div(words, 200) + 1}
end
end
end
end
Functional Interface
MyApp.Blog.Post.reading_time("content string")
- Takes:
- content: String
- Returns:
- Integer (minutes)
reading_time("A long blog post...") → 3
Persistence
Step 2 of 9
Now let's add state to support persistent storage, while keeping our existing behavior. Your resource now combines behavior and state. The original action still works exactly the same, plus you can create and persist posts.
defmodule MyApp.Blog.Post do
use Ash.Resource
data_layer: AshPostgres.DataLayer
postgres do
table "posts"
repo MyApp.Repo
end
attributes do
uuid_primary_key :id
attribute :title, :string,
allow_nil?: false
attribute :content, :string
attribute :status, :atom,
constraints: [
one_of: [:draft, :published]
]
timestamps()
end
# ...
end
PostgreSQL Table: posts
| Column | Type |
|---|---|
| id | uuid |
| title | text |
| content | text |
| status | text |
| created_at | timestamp |
| updated_at | timestamp |
GraphQL
Step 3 of 9
Add a full GraphQL API with minimal configuration. The extension automatically understands your existing actions and generates queries, mutations, and types.
defmodule MyApp.Blog.Post do
use Ash.Resource
extensions: [AshGraphql.Resource]
graphql do
type :post
queries do
get :post, :read
list :posts, :read
end
mutations do
create :create_post, :create
update :update_post, :update
destroy :delete_post, :destroy
end
end
# ...
end
GraphQL API
query GetPost($id: ID!) {
post(id: $id) {
id
title
}
}
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
id
title
}
}
JSON:API
Step 4 of 9
Add a REST JSON:API alongside your GraphQL API. All API extensions work on top of your resources and actions. Ash makes it trivial to expose your domain through multiple API standards, letting clients choose the interface that works best for them.
defmodule MyApp.Blog.Post do
use Ash.Resource
extensions: [
AshGraphql.Resource,
AshJsonApi.Resource
]
json_api do
type "post"
routes do
base "/posts"
get :read
index :read
post :create
patch :update
end
end
# ...
end
JSON:API REST Endpoints
- GET /posts
- GET /posts/:id
- POST /posts
- PATCH /posts/:id
{
"data": {
"type": "post",
"id": "123",
"attributes": {
"title": "My Post",
"content": "Post content..."
}
}
}
Policies
Step 5 of 9
Add fine-grained authorization with policies. Control who can perform which actions and which data they can see. Policies can forbid actions or filter data transparently for reads, preventing enumeration attacks.
defmodule MyApp.Support.Ticket do
use Ash.Resource
extensions: [
# ...
],
authorizers: [Ash.Policy.Authorizer]
policies do
# Only admins can create tickets
policy action_type(:create) do
authorize_if expr(
^actor(:role) == :admin
)
end
# Users can only read their own tickets
policy action_type(:read) do
authorize_if expr(
user_id == ^actor(:id)
)
end
end
# ...
end
Authorization Policies
policy action_type(:create) do
authorize_if expr(
^actor(:role) == :admin
)
end
MyApp.Support.open_ticket!(
actor: non_admin
)
# Forbidden
policy action_type(:read) do
authorize_if expr(
user_id == ^actor(:id)
)
end
MyApp.Tickets.list_tickets(
actor: user
)
# SQL: SELECT * FROM tickets
# WHERE user_id = $1
Encryption
Step 6 of 9
Add encryption at rest with our Cloak integration. Your post content is now automatically encrypted when stored and decrypted when read, with no changes to your existing API or business logic. The encryption is completely transparent to your application code.
defmodule MyApp.Blog.Post do
use Ash.Resource
extensions: [
AshGraphql.Resource,
AshJsonApi.Resource,
AshCloak.Resource
]
cloak do
vault MyApp.Vault
# Automatically encrypt content
attributes [:content]
end
# ...
end
Encryption at Rest
Content field automatically encrypted/decrypted
AI Tools
Step 7 of 9
Add AI-powered actions and expose your domain as tools for LLMs. Ash AI lets you create prompt-backed actions that delegate to AI models, and exposes your existing actions as tools that AI agents can use. Perfect for building AI-powered features or MCP (Model Context Protocol) servers.
defmodule MyApp.Blog.Post do
use Ash.Resource
extensions: [
#...,
AshAi
]
actions do
# AI-powered action
action :analyze_sentiment, :atom do
constraints [
one_of: [:positive, :negative]
]
argument :content, :string,
allow_nil?: false
run prompt(
ChatOpenAI.new!(
%{model: "gpt-4o"}
)
)
end
end
#...
end
AI-Powered Actions & Tools
analyze_sentiment(content: "Great post!")
→ :positive
MCP Tools Exposed
- create_post
- read_posts
- update_post
- analyze_sentiment
Authentication
Step 8 of 9
Add complete user authentication with password and OAuth strategies. AshAuthentication provides built-in support for password authentication, OAuth providers (GitHub, Google, etc.), magic links, and more.
defmodule MyApp.Accounts.User do
use Ash.Resource
extensions: [AshAuthentication]
authentication do
strategies do
password do
# ...
end
magic_link do
# ...
end
end
end
# ...
end
User Authentication
Authentication Strategies
- Password
- Magic Link
- Api Key
- GitHub
- Slack
- Custom
Background Jobs
Step 9 of 9
Add background job processing with AshOban. Define triggers that run periodically for records matching conditions, and scheduled actions that run on cron schedules. Perfect for notifications, data processing, and maintenance tasks.
defmodule MyApp.Blog.Post do
use Ash.Resource
extensions: [
# ...
AshOban
]
oban do
triggers do
trigger :publish do
where expr(state == :pending)
scheduler_cron "@hourly"
end
end
scheduled_actions do
schedule :cleanup_old_drafts,
"@daily"
end
end
# ...
end
Background Job Processing
Triggers
Run actions for records matching conditions on a schedule
publish Runs hourly to publish pending posts
Scheduled Actions
Run generic actions on cron schedules
cleanup_old_drafts Runs daily to clean up old draft posts
The Ash Ecosystem
Powerful extensions that integrate seamlessly with your resources.
-
Ash (opens in a new tab)
Core framework
-
AshPostgres (opens in a new tab)
PostgreSQL data layer
-
AshPhoenix (opens in a new tab)
Phoenix integration
-
AshGraphQL (opens in a new tab)
GraphQL API extension
-
AshJsonApi (opens in a new tab)
JSON:API extension
-
Reactor (opens in a new tab)
Workflows & Sagas
-
AshAuthentication (opens in a new tab)
Authentication
-
AshAdmin (opens in a new tab)
Admin interface
Proud Partner
(opens in a new tab)
The EEF is the 501(c)(3) non-profit that stewards the BEAM ecosystem we all build on. Their working groups make Elixir, Erlang, and the wider BEAM safer and more sustainable for everyone.
-
Securing the Ecosystem
The EEF Security Working Group identifies vulnerabilities, publishes guidance, and funds independent audits — including the recent Hex.pm security audit that protects every package in the ecosystem, Ash included.
-
Supporting the Community
Working groups for build tools, observability, education, ML, and infrastructure — plus fellowships, stipends, conferences, and the documentation that newcomers and experts rely on every day.
-
Becoming a Member
Individuals and companies can join as members starting at modest annual dues. Membership funds the working groups directly, gives you a voice in governance, and keeps the BEAM independent and community-driven.
Learn more at erlef.org (opens in a new tab) — every contribution and member helps secure and sustain the BEAM.
One Command to a Working App
Pick a preset or choose features one by one. The installer uses Igniter to set it all up.
sh <(curl 'https://ash-hq.org/install/lemonade_stand') && cd lemonade_stand
Trusted in Production
(opens in a new tab)
(opens in a new tab)
(opens in a new tab)
(opens in a new tab)
(opens in a new tab)
(opens in a new tab)
(opens in a new tab)
(opens in a new tab)
(opens in a new tab)(opens in a new tab)
(opens in a new tab)
(opens in a new tab)(opens in a new tab)
(opens in a new tab)
(opens in a new tab)