Creating Backpex Resource Actions
You are an expert at creating resource actions for Backpex. Resource actions operate on the resource as a whole (not individual items) and appear as buttons in the index toolbar. They open a slide-over with a form.
Required Callbacks
| Callback |
Signature |
Description |
title/0 |
-> string |
Slide-over title |
label/0 |
-> string |
Button text in index toolbar |
fields/0 |
-> keyword list |
Form field definitions |
changeset/3 |
(change, attrs, metadata) -> changeset |
Validate form data. metadata has :assigns and :target keys |
handle/2 |
(socket, data) -> {:ok, socket} | {:error, changeset} |
Execute the action with validated data |
Optional Callbacks
| Callback |
Default |
Description |
base_schema/1 |
schemaless changeset |
Override to use a real Ecto schema |
Example: Simple Resource Action
defmodule MyAppWeb.ResourceActions.InviteUser do
use Backpex.ResourceAction
import Ecto.Changeset
@impl Backpex.ResourceAction
def title, do: "Invite User"
@impl Backpex.ResourceAction
def label, do: "Invite"
@impl Backpex.ResourceAction
def fields do
[
email: %{
module: Backpex.Fields.Text,
label: "Email",
type: :string
},
role: %{
module: Backpex.Fields.Select,
label: "Role",
options: [Admin: "admin", User: "user"],
prompt: "Select role...",
type: :string
}
]
end
@impl Backpex.ResourceAction
def changeset(change, attrs, _metadata) do
change
|> cast(attrs, [:email, :role])
|> validate_required([:email, :role])
|> validate_format(:email, ~r/@/)
end
@impl Backpex.ResourceAction
def handle(socket, data) do
case MyApp.Accounts.send_invitation(data.email, data.role) do
:ok ->
{:ok, Phoenix.LiveView.put_flash(socket, :info, "Invitation sent to #{data.email}.")}
{:error, reason} ->
{:ok, Phoenix.LiveView.put_flash(socket, :error, "Failed: #{reason}")}
end
end
end
Example: Export Action
defmodule MyAppWeb.ResourceActions.ExportPosts do
use Backpex.ResourceAction
import Ecto.Changeset
@impl Backpex.ResourceAction
def title, do: "Export Posts"
@impl Backpex.ResourceAction
def label, do: "Export"
@impl Backpex.ResourceAction
def fields do
[
format: %{
module: Backpex.Fields.Select,
label: "Format",
options: [CSV: "csv", JSON: "json"],
prompt: "Select format...",
type: :string
}
]
end
@impl Backpex.ResourceAction
def changeset(change, attrs, _metadata) do
change
|> cast(attrs, [:format])
|> validate_required([:format])
|> validate_inclusion(:format, ["csv", "json"])
end
@impl Backpex.ResourceAction
def handle(socket, data) do
# Trigger export...
{:ok, Phoenix.LiveView.put_flash(socket, :info, "Export started in #{data.format} format.")}
end
end
Wiring Into a LiveResource
@impl Backpex.LiveResource
def resource_actions do
[
invite: %{module: MyAppWeb.ResourceActions.InviteUser},
export: %{module: MyAppWeb.ResourceActions.ExportPosts}
]
end
The keyword key (e.g. :invite) is used as the action identifier for routing and authorization via can?/3.
Key Differences From Item Actions
| Aspect |
Resource Action |
Item Action |
| Scope |
Whole resource |
Selected items |
| UI |
Slide-over form |
Modal dialog |
| Callbacks |
title/0, label/0, handle/2 |
icon/2, label/2, handle/3 |
| Form |
Always has fields |
Optional |
| Location |
Index toolbar only |
Row, index toolbar, show page |
Conventions
- File location:
lib/my_app_web/resource_actions/<snake_case_name>.ex
- Module naming:
MyAppWeb.ResourceActions.<ActionName>
- Always include
type: key in each field map (e.g. type: :string). This is required for the schemaless changeset to work.
- Authorization is handled via
can?(assigns, :action_key, nil) in the LiveResource (item is always nil)
- Return
{:error, changeset} from handle/2 to keep the form open and show validation errors
1---2name: create-resource-action3description: Use when creating Backpex resource actions for global operations like bulk exports, invitations, imports, or any action that applies to the resource as a whole rather than individual items.4---56# Creating Backpex Resource Actions78You are an expert at creating resource actions for Backpex. Resource actions operate on the resource as a whole (not individual items) and appear as buttons in the index toolbar. They open a slide-over with a form.910## Required Callbacks1112| Callback | Signature | Description |13|----------|-----------|-------------|14| `title/0` | `-> string` | Slide-over title |15| `label/0` | `-> string` | Button text in index toolbar |16| `fields/0` | `-> keyword list` | Form field definitions |17| `changeset/3` | `(change, attrs, metadata) -> changeset` | Validate form data. `metadata` has `:assigns` and `:target` keys |18| `handle/2` | `(socket, data) -> {:ok, socket} \| {:error, changeset}` | Execute the action with validated data |1920## Optional Callbacks2122| Callback | Default | Description |23|----------|---------|-------------|24| `base_schema/1` | schemaless changeset | Override to use a real Ecto schema |2526## Example: Simple Resource Action2728```elixir29defmodule MyAppWeb.ResourceActions.InviteUser do30 use Backpex.ResourceAction3132 import Ecto.Changeset3334 @impl Backpex.ResourceAction35 def title, do: "Invite User"3637 @impl Backpex.ResourceAction38 def label, do: "Invite"3940 @impl Backpex.ResourceAction41 def fields do42 [43 email: %{44 module: Backpex.Fields.Text,45 label: "Email",46 type: :string47 },48 role: %{49 module: Backpex.Fields.Select,50 label: "Role",51 options: [Admin: "admin", User: "user"],52 prompt: "Select role...",53 type: :string54 }55 ]56 end5758 @impl Backpex.ResourceAction59 def changeset(change, attrs, _metadata) do60 change61 |> cast(attrs, [:email, :role])62 |> validate_required([:email, :role])63 |> validate_format(:email, ~r/@/)64 end6566 @impl Backpex.ResourceAction67 def handle(socket, data) do68 case MyApp.Accounts.send_invitation(data.email, data.role) do69 :ok ->70 {:ok, Phoenix.LiveView.put_flash(socket, :info, "Invitation sent to #{data.email}.")}7172 {:error, reason} ->73 {:ok, Phoenix.LiveView.put_flash(socket, :error, "Failed: #{reason}")}74 end75 end76end77```7879## Example: Export Action8081```elixir82defmodule MyAppWeb.ResourceActions.ExportPosts do83 use Backpex.ResourceAction8485 import Ecto.Changeset8687 @impl Backpex.ResourceAction88 def title, do: "Export Posts"8990 @impl Backpex.ResourceAction91 def label, do: "Export"9293 @impl Backpex.ResourceAction94 def fields do95 [96 format: %{97 module: Backpex.Fields.Select,98 label: "Format",99 options: [CSV: "csv", JSON: "json"],100 prompt: "Select format...",101 type: :string102 }103 ]104 end105106 @impl Backpex.ResourceAction107 def changeset(change, attrs, _metadata) do108 change109 |> cast(attrs, [:format])110 |> validate_required([:format])111 |> validate_inclusion(:format, ["csv", "json"])112 end113114 @impl Backpex.ResourceAction115 def handle(socket, data) do116 # Trigger export...117 {:ok, Phoenix.LiveView.put_flash(socket, :info, "Export started in #{data.format} format.")}118 end119end120```121122## Wiring Into a LiveResource123124```elixir125@impl Backpex.LiveResource126def resource_actions do127 [128 invite: %{module: MyAppWeb.ResourceActions.InviteUser},129 export: %{module: MyAppWeb.ResourceActions.ExportPosts}130 ]131end132```133134The keyword key (e.g. `:invite`) is used as the action identifier for routing and authorization via `can?/3`.135136## Key Differences From Item Actions137138| Aspect | Resource Action | Item Action |139|--------|----------------|-------------|140| Scope | Whole resource | Selected items |141| UI | Slide-over form | Modal dialog |142| Callbacks | `title/0`, `label/0`, `handle/2` | `icon/2`, `label/2`, `handle/3` |143| Form | Always has fields | Optional |144| Location | Index toolbar only | Row, index toolbar, show page |145146## Conventions147148- **File location**: `lib/my_app_web/resource_actions/<snake_case_name>.ex`149- **Module naming**: `MyAppWeb.ResourceActions.<ActionName>`150- **Always include `type:` key** in each field map (e.g. `type: :string`). This is required for the schemaless changeset to work.151- **Authorization** is handled via `can?(assigns, :action_key, nil)` in the LiveResource (item is always `nil`)152- **Return `{:error, changeset}`** from `handle/2` to keep the form open and show validation errors