Custom Action
Overviewâ
Custom actions let you add your own buttons, links, modals, and pages to a model beyond the default index, show, new, edit, and destroy actions. They are defined with the custom_action DSL method inside a model's cm-admin configuration and can run arbitrary Ruby code against a record (or the whole collection).
Typical use cases:
- State transitions â approve, archive, publish, or soft-delete a record.
- Quick edits â update a subset of fields through a modal or dedicated page.
- Exports / bulk fetches â trigger a collection-level operation.
- External links â send the user to another URL related to the record.
Syntaxâ
custom_action name: 'action_name', route_type: 'member', verb: 'patch',
path: ':id/action_name', display_type: :button do
record = ::YourModel.find(params[:id])
# perform the operation
record
end
The block receives access to params and must return the affected record (or collection). The returned object is used to render the response and to evaluate success_message / error_message.
Optionsâ
| Option | Type | Description |
|---|---|---|
name | String | Unique identifier for the action. Used to build the route and controller method. |
display_name | String | Human-friendly label shown on the button/link. Falls back to name.titleize. |
verb | String | HTTP verb: get, post, put, patch, or delete. |
path | String | URL path. Use :id for member routes (e.g. ':id/approve'). |
route_type | String | member (operates on a single record) or collection (operates on many). |
display_type | Symbol | How the action is rendered. See Display types. |
display_if | Proc | Lambda receiving the record; return true to show the action. |
icon_name | String | Font Awesome icon class, e.g. 'fa-regular fa-circle-check'. |
icon_style | String | Inline icon style, e.g. '--fa-primary-color: #ff6b6b;'. |
modal_configuration | Hash | Modal title, description, and confirmation_text for modal display types. |
page_title | String | Title shown for :page / :form_page display types. |
page_description | String | Description shown for :page / :form_page display types. |
group_name | String | Groups actions together in the dropdown (see Group Action). |
show_on_index | Boolean | Whether the action appears in the index row dropdown. Defaults to true. |
redirect_to | Proc | Lambda returning a URL to redirect to (used by :link). |
success_message | Proc | Lambda receiving the record; returns an alert Hash { header:, body:, icon: }. |
error_message | Proc | Lambda receiving the record and its errors; returns an alert Hash { header:, body:, icon: }. |
alert_type | Symbol | Success alert style: :flash (default) or :banner. |
Display typesâ
The display_type option controls how the action is presented:
:buttonâ A button that submits directly and runs the block. Best for one-click state changes.:modalâ Shows a confirmation modal before submitting. Configure copy viamodal_configuration.:form_modalâ Opens a modal containing a form. Requires aformblock and anon_submitblock.:pageâ Renders a dedicated page. Usepage_title/page_description.:form_pageâ A full-page form. Requires aformblock and anon_submitblock.:linkâ Renders a link to an external/related URL viaredirect_to.:icon_onlyâ Renders only the icon with no label.
Examplesâ
Button actionâ
Runs immediately when clicked. Here display_if hides the action unless the record is a draft.
custom_action name: 'activate', display_name: 'Activate', route_type: 'member', verb: 'post',
icon_name: 'fa-regular fa-power-off', path: ':id/activate',
display_type: :button, display_if: ->(df) { !df.is_active? } do
demo_form = ::DemoForm.find(params[:id])
demo_form.update!(is_active: true)
demo_form
end
Modal actionâ
Prompts the user to confirm before running the block.
custom_action name: 'approve', display_name: 'Approve', route_type: 'member', verb: 'patch',
icon_name: 'fa-regular fa-circle-check', path: ':id/approve',
display_type: :modal, display_if: lambda(&:draft?),
modal_configuration: { title: 'Approve Demo Form',
description: 'Are you sure you want to approve this demo form?',
confirmation_text: 'Approve' } do
demo_form = ::DemoForm.find(params[:id])
demo_form.approved!
demo_form
end
Form modal actionâ
Collects input through a form rendered in a modal. Requires both a form block (defining the fields) and an on_submit block (handling the update).
custom_action name: 'assign_user', display_name: 'Assign User', verb: 'post', route_type: 'member',
icon_name: 'fa-regular fa-user', display_type: :form_modal,
path: ':id/assign_user',
modal_configuration: { title: 'Select User', confirmation_text: 'Assign' } do
form do
cm_section '' do
alert_box body: 'This will assign a new user to the demo form.', type: :info
form_field :user_id, input_type: :radio_button_group, helper_method: :select_options_for_user, label: 'User'
end
end
on_submit do
@demo_form = ::DemoForm.find(params[:id])
@demo_form.update!(user_id: params.dig(:demo_form, :user_id))
@demo_form
end
end
Note: For
:form_modaland:form_page, theformblock is mandatory. Submitted params are nested under the model name, e.g.params[:demo_form][:user_id].
Collection actionâ
Use route_type: 'collection' for actions that operate on the whole collection rather than a single record. These paths do not include :id.
custom_action name: 'export_all', display_name: 'Export All', route_type: 'collection', verb: 'get',
icon_name: 'fa-regular fa-download', path: 'export_all',
display_type: :button do
::DemoForm.all
end
Styled actionâ
Customize the icon appearance with icon_style.
custom_action name: 'styled_action', display_name: 'Styled Action', route_type: 'member', verb: 'post',
icon_name: 'fa-regular fa-palette',
icon_style: '--fa-primary-color: #ff6b6b; --fa-secondary-color: #4ecdc4;',
path: ':id/styled_action', display_type: :button do
demo_form = ::DemoForm.find(params[:id])
demo_form
end
Custom success and error messagesâ
success_message and error_message are lambdas that return an alert Hash with :header, :body, and :icon keys. success_message receives the record; error_message receives the record and its errors. Any key you omit falls back to a sensible default.
custom_action name: 'approve', display_name: 'Approve', route_type: 'member', verb: 'patch',
icon_name: 'fa-regular fa-circle-check', path: ':id/approve',
display_type: :button,
success_message: lambda { |demo_form|
{ header: 'Approved', body: "#{demo_form.name} was approved.", icon: 'fa fa-check-circle' }
},
error_message: lambda { |_demo_form, errors|
{ header: 'Approval failed', body: errors.full_messages.to_sentence, icon: 'fa fa-circle-xmark' }
} do
demo_form = ::DemoForm.find(params[:id])
demo_form.approved!
demo_form
end
Relatedâ
- Group Action â group multiple custom actions under a common heading.
- Bulk Action â run an action across multiple selected records.
- Adding Alert â configure success and error alerts.