npx claudepluginhub naymspace/backpexThis skill uses the workspace's default tool permissions.
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.
Creates custom Backpex item actions for adding buttons to table rows or show pages, or modifying default edit/delete/show actions. Covers handle/link callbacks, icons, labels, and optional forms.
Generates FilamentPHP v4 actions for resources, pages, and tables including modals, confirmations, forms, and bulk operations.
Provides Phoenix authorization patterns for LiveViews and controllers: server-side ownership checks in handle_event, policy modules, scoped queries to prevent IDOR.
Share bugs, ideas, or general feedback.
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.
| 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 |
| Callback | Default | Description |
|---|---|---|
base_schema/1 | schemaless changeset | Override to use a real Ecto schema |
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
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
@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.
| 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 |
lib/my_app_web/resource_actions/<snake_case_name>.exMyAppWeb.ResourceActions.<ActionName>type: key in each field map (e.g. type: :string). This is required for the schemaless changeset to work.can?(assigns, :action_key, nil) in the LiveResource (item is always nil){:error, changeset} from handle/2 to keep the form open and show validation errors