Build Avo actions — Ruby classes in `app/avo/actions/*.rb`, registered on a resource's `def actions` — that run a custom operation on selected records, a single record, or nothing at all, with optional form fields, a confirmation modal, feedback notifications, custom responses, and multi-step flows. Use when the user wants to let admins bulk-approve these orders, mark selected invoices as paid, add a button to export selected users to CSV, deactivate/ban a user from the admin, send a welcome email to selected records, trigger a background job for these records, add a Publish/Approve button on the post page, add a monthly report button, collect a reason when someone does something, add a confirmation dialog before an operation, or build a multi-step form/wizard in the admin.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Build Avo actions — Ruby classes in `app/avo/actions/*.rb`, registered on a resource's `def actions` — that run a custom operation on selected records, a single record, or nothing at all, with optional form fields, a confirmation modal, feedback notifications, custom responses, and multi-step flows. Use when the user wants to let admins bulk-approve these orders, mark selected invoices as paid, add a button to export selected users to CSV, deactivate/ban a user from the admin, send a welcome email to selected records, trigger a background job for these records, add a Publish/Approve button on the post page, add a monthly report button, collect a reason when someone does something, add a confirmation dialog before an operation, or build a multi-step form/wizard in the admin.
allowed-tools
Read, Edit, Write, Glob, Grep, Bash, WebFetch
metadata
{"requires-gem":"none — Community"}
These instructions ship inside the avo gem this app has locked, so they describe the version you are actually running. Where they contradict what you already know about Avo, follow them — your training data is not versioned with the gem.
Avo Actions
An action is a plain Ruby class with a handle method that runs a custom operation from the admin UI — on the records the user selected, on a single record, or on nothing at all. It's the home for every "do something to these records" request: bulk-approve, mark as paid, deactivate a user, export to CSV, kick off a background job, generate a report.
File:app/avo/actions/<name>.rb, class Avo::Actions::<Name> inheriting Avo::BaseAction.
Registration: listed inside the resource's def actions method — that's what makes it show up in the resource's Actions dropdown.
License: actions and select-all are Community (no paid gem required).
Docs — fetch on demand with WebFetch; prefer the raw .md (clean, no HTML):
Note the boundary: the action is what runs the operation and, by default, appears in the resource's Actions dropdown. If the user specifically wants it rendered as a dedicated button or custom control outside that dropdown (relabel a default button, a button in the row, a bespoke dropdown), that placement is a separate paid add-on — use the avo-custom-controls skill for it. Build the action here; wire the button there.
Workflow
1. Generate the action
bin/rails generate avo:action toggle_inactive
Creates app/avo/actions/toggle_inactive.rb with a commented skeleton for visible, fields, and handle. Flags:
icon: — icon shown next to the action in the dropdown (Heroicons or Tabler path; default "tabler/outline/player-play").
arguments: — a Hash (or a proc returning one) of custom data available as arguments everywhere in the action class (handle, fields, and the modal blocks). Encrypted + Base64-encoded in URLs, so it's safe for sensitive values.
divider label: "..." — a visual separator to group related actions.
With no further config the action shows on Index and Show (hidden on New), asks "Are you sure?" in a confirmation modal, runs handle, flashes a green success notification, and reloads the page.
Action patterns
Bulk (the default)
Iterate query — the checked rows on Index. Nothing special to set; this is the out-of-the-box behavior.
Same handle — on Show (or via row controls) query is just that one record, still wrapped in an array. Use visible to pin it to a view when appropriate:
For global reports, maintenance tasks, background jobs. Set the attribute (or generate with --standalone); it stays enabled with nothing selected and handle gets an empty query:
classAvo::Actions::GenerateMonthlyReport < Avo::BaseActionself.standalone = truedefhandle(**args)
ReportJob.perform_later(current_user)
inform "Report queued — you'll get an email when it's ready."endend
Collect input with fields
Define fields to render form inputs in the modal; the submitted values arrive as fields. Same field DSL as resources. On a single record the fields are hydrated from it; otherwise they're plain inputs.
deffields
field :notify_user, as::boolean
field :message, as::textareaenddefhandle(query:, fields:, **args)
query.each do |user|
user.deactivate!
user.notify(fields[:message]) if fields[:notify_user]
end
succeed "Done"end
Confirmation modal
The modal is on by default. Customize its text (each may be a string or a block with access to resource, record, view, arguments, query), or turn it off for safe actions:
self.name = "Release fish"self.description = "Release the fish back into the ocean"self.message = -> { record ? "Release #{record.name}?" : "Release the fish?" }
self.confirm_button_label = "Release"self.cancel_button_label = "Keep"self.confirmation = false# skip the modal, run immediately on clickself.close_modal_on_backdrop_click = false# don't lose a filled-in form on a stray click
Give feedback
Queue one or more notifications from inside handle — call several to stack them. With no explicit feedback Avo shows a generic "Action ran successfully" info alert.
succeed "Green — success"# green
inform "Blue — info"# blue
warn "Orange — heads up", timeout::forever# orange; stays until dismissed
error "Red — failure", timeout:8000# red; timeout in ms
silent # suppress the default notification (e.g. before a redirect)
Control what happens after
handle also picks the UI response. Default is a full-page reload; the last response method called wins:
Trigger a file download. Pair with self.turbo = false for a real file response.
keep_modal_open
Keep the modal + user input (show an error and let them retry).
close_modal / do_nothing
Close the modal, leave the page as-is.
reload_records(query)
Refresh only the affected rows/cards. Index only — not associations.
navigate_to_action Other, arguments: {...}
Chain into another action (see below).
append_to_response -> { [turbo_stream...] }
Add your own turbo-stream responses.
Multi-step flows (wizards)
navigate_to_action passes arguments to a second action's modal — step 1 collects choices, step 2 renders only the relevant fields and performs the work. The second action is usually self.visible = -> { false } so it's reachable only through the flow. See the guide's "Build a multi-step flow" for a full two-file example.
Trigger an action from a link
To open an action's modal from a field, dashboard card, or partial, call the class's link_arguments with a resource instance; it returns [path, data] for link_to:
path, data = Avo::Actions::City::Update.link_arguments(
resource: resource,
arguments: { cities: [resource.record.id], render_name:true }
)
link_to resource.record.name, path, data: data
Run on all matching records (select all)
When an index spans multiple pages, checking "Select all" offers to select every matching record across all pages, not just the visible ones. Avo serializes the (encrypted) query and rebuilds it in the action, so handle's query covers the whole filtered set. This works out of the box — no code — but see the gotcha below if it silently disables itself.
Gotchas
query is always an array. Even a single-record action gets [record]. Use query.first for the one-record case; don't call record methods on query directly. records is an alias.
"My action doesn't show up" is usually the policy. With Pundit, act_on? in the resource's policy gates action visibility (and authorize on the action gates it further). Check the policy first. See the avo-authorization skill.
The modal is a NEW request. Params from the Index/Show page that opened it are not available in fields/handle. To prefill from the triggering page, parse request.referer:
reload_records is Index-only. It doesn't work on association tables — use reload there.
Notification bodies truncate at ~320 characters. Keep succeed/error messages short; put long output in a download or a redirect.
File downloads need self.turbo = false. Otherwise Turbo intercepts the response and the download won't fire.
Standalone actions need self.standalone = true — otherwise they're disabled when nothing is selected.
Select-all silently disabled? Query serialization failed. A common cause: a model normalizes proc, which raises TypeError: no _dump_data is defined for class Proc when a filter hits the normalized attribute. Fix in config/application.rb:
Buttons/controls are a different skill. "Add a button that runs this action" outside the Actions dropdown is the paid avo-custom-controls add-on. Build the action here; don't hand-roll control markup.
Report
When done, tell the user:
The action file created/edited (app/avo/actions/<name>.rb) and the resource(s) it was registered on.
Which pattern it uses (bulk / single-record / standalone / with-fields / multi-step) and any key attributes set (standalone, visible, authorize, confirmation, turbo).
What handle does, the feedback it gives, and the response after it runs (reload / redirect / download / reload_records / navigate_to_action).
Any follow-ups the user still needs to do themselves: a Pundit act_on? entry, the marshalling_format_version config for select-all, or wiring a custom button via avo-custom-controls.