Core Action
Self-contained, type-safe backend operations with automatic UI rendering and zero network overhead for metadata.Key Features
- Self-Contained: All metadata (label, icon, variant, description) lives in the action class
- Type-Safe: Automatic TypeScript type generation with IDE autocomplete
- Zero Network Overhead: Metadata is compiled at build time, not fetched at runtime
- Automated Rendering: Actions automatically appear in AppBar when passed to
AppPage->actions() - Built-in: Authorization, validation, and confirmation dialogs
Quick Start
1. Create an Action
2. Use in Controller
3. Handle on Frontend
->withSuccess() and ->withError() redirects. You can still add custom onSuccess or onError callbacks if you need to perform additional frontend actions.
Important Details
- Model Binding: Only model IDs are sent to the frontend. The backend automatically resolves them back to model instances in
handle()using type hints. - Toast Messages: Handle success/error via
->withSuccess()and->withError()in PHP, not manually in the frontend. - Authorization: Use
getParam('key', Model::class)inauthorize()to auto-resolve models for permission checks.
Action Properties
| Property | Type | Method | Description |
|---|---|---|---|
key | string | key() | Unique identifier (auto-generated from class name) |
label | string | label() | Button label shown in UI |
icon | string | icon() | Icon identifier (e.g., ‘Mail’, ‘Trash’) |
variant | CoreVariant | variant() | Button style (brand, destructive, default, etc.) |
description | string | description() | Action description for tooltips/docs |
confirm | bool | confirm() | Whether to show confirmation dialog |
confirmMessage | string | confirmMessage() | Custom confirmation message |
hidden | bool | hidden() | Whether to hide the action from the UI |
withTrashed | bool|array<string, bool> | withTrashed() | Include soft-deleted models when resolving bindings |
params | array | params() | Default parameters from backend |
Frontend Component Props
CoreActionButton
| Prop | Type | Description |
|---|---|---|
action | CoreActionData | The action data object |
params | any[] | Static parameters to override backend defaults |
getParams | () => any[] | Function to compute params dynamically at execution time |
onSuccess | () => void | Callback fired on successful execution |
onError | (error: any) => void | Callback fired on error |
onValidationError | (errors: any) => void | Callback fired on validation errors |
inline | boolean | Use inline styling for dropdowns/menus |
buttonProps | ButtonProps | Custom props passed to underlying button |
CoreActionForm
For form-based action execution with automatic validation error handling, loading states, and confirmation dialogs, useCoreActionForm. It extends CoreForm and provides seamless integration with actions.
See: CoreActionForm Documentation for complete form integration guide.
AppPage actionCallbacks
Parameter Handling
Parameters follow a clear priority order (highest to lowest):getParams()- Dynamic params computed at execution time (frontend)paramsprop - Static params passed from frontendparams()- Default params set from backend controller
Backend Default Parameters
Frontend Static Parameters
Dynamic Parameters with getParams
Model Binding
Actions automatically resolve Eloquent models from IDs using type hints in thehandle() method.
How It Works
Frontend receives only IDs: When you pass a model instance to an action, only the model’s ID is serialized and sent to the frontend. The backend automatically converts IDs back to model instances when the action executes. Backend automatically resolves: If yourhandle() method has a type-hinted Eloquent model parameter, the action will automatically resolve it using findOrFail():
Including Soft-Deleted Models
By default, actions will not resolve soft-deleted models. To include trashed models when resolving model bindings, use thewithTrashed() method. This follows Laravel’s route model binding pattern where you explicitly opt-in to include trashed models.
Global (all models):
Using getParam() in Authorization
The getParam() protected method is useful in the authorize() method to automatically resolve models:
getParam() will automatically resolve the Eloquent model instance from the ID using findOrFail().
Named Parameters
Use associative arrays for clearer parameter handling:Action Dropdowns
Group multiple actions in a dropdown:Primary Actions
Mark actions or dropdowns as primary to show them in the page header. Non-primary actions will only appear in the AppBar (sticky header that appears on scroll):Primary Actions Behavior
For individual actions:- Actions marked with
->primary()appear as buttons in the page header
- If the dropdown itself is marked as
->primary(): The entire dropdown appears in the header - If individual actions inside are marked as
->primary(): Those actions are extracted and shown as individual buttons (not in a dropdown)
- When primary actions are extracted from a dropdown that contains additional non-primary actions, a vertical dashed border separator appears between the title and action buttons to indicate more actions are available in the AppBar
Passing Actions Via ->with()
Sometimes you want to pass an action to the frontend without registering it in the AppBar. Use->with() to pass actions as data:
Standalone Usage
Using CoreActionButton
Using CoreActionForm
For form-based action execution, useCoreActionForm. It provides automatic validation error handling, loading states, and confirmation dialogs.
See: CoreActionForm Documentation for complete usage examples and API reference.
useCoreAction Hook
For React components that need loading state management:executeCoreAction Function
For imperative execution in callbacks, event handlers, or anywhere hooks can’t be used:useMemocallbacks where hooks can’t be called- Event handlers that don’t need loading state
- Quick actions in global search
- Any imperative context outside React’s render cycle
Return Types
Actions can return various response types:Complete Example
Backend Action:TypeScript Types
Run to generate TypeScript types:resources/js/types/generated.d.ts for IDE autocomplete.
Hiding Actions
Actions can be hidden from the UI while still being executable programmatically. Usehidden() to hide an action:
- Are not rendered in the AppBar
- Are not included in global search quick actions
- Are not shown in dropdown menus
- Can still be executed programmatically if accessed directly
WithTableAction Trait
For actions that operate on table rows (single or bulk), use theWithTableAction trait. It provides automatic authorization, validation, and query building - similar to how WithTableQuery works for Form Requests.
When to Use WithTableAction
UseWithTableAction when:
- Your action operates on one or more table rows
- You need to support both row actions (single item) and bulk actions (multiple items)
- You want automatic
id/idsvalidation - You want per-model authorization checks
Basic Usage
Trait Methods
| Method | Type | Description |
|---|---|---|
getTable() | Required | Returns table class for model inference and * (select all) support |
authorizeModel(Model $model) | Optional | Per-model authorization check (default: true) |
authorizeBulk() | Optional | Bulk authorization check for performance (default: null) |
authorize() | Provided | Iterates all targets, calls authorizeModel() for each |
rules() | Provided | Returns idRules() by default, override to add custom rules |
idRules() | Provided | Returns validation rules for id/ids parameters |
getTableQuery() | Provided | Query builder scoped to target IDs |
getModel() | Provided | Get single model (for single-item actions) |
getModels() | Provided | Get all target models as collection |
getCount() | Provided | Get count of affected records |
processModels() | Provided | Execute callback on single/bulk models, auto-tracks count |
getBulkMessage() | Provided | Generate success message (singular/plural, count-aware) |
backWithSuccess() | Provided | Redirect with success message (uses getBulkMessage()) |
isBulkOperation() | Provided | Check if action has ids array |
hasSingleId() | Provided | Check if action has single id |
Handling Single vs Bulk Operations
The trait automatically handles both single (id) and bulk (ids) operations. Use processModels() for clean, automatic handling:
Using processModels() Helper
TheprocessModels() method is the recommended way to handle both single and bulk operations. It simplifies your code by automatically:
- Detecting whether it’s a single or bulk operation
- Executing your callback on each model
- Tracking the count for success messages
- Providing a consistent pattern across all table actions
- Cleaner, more readable code
- Automatic count tracking for
getBulkMessage() - Consistent pattern across all table actions
- No need for conditional logic or manual counting
- Easier to maintain and test
- Single: “The user was restored successfully”
- Bulk: “5 users were restored successfully”
Customizing Success Messages
OverridegetBulkMessage() to customize the success message format:
- Count = 1: Uses
$rowSingular(e.g., “The user was restored”) - Count > 1: Uses
$rowPlural(e.g., “5 users were restored”)
Manual Query Iteration (Alternative)
If you need more control thanprocessModels() provides, you can manually iterate with getTableQuery():
setMessageCount() before backWithSuccess(). Use processModels() instead for automatic count tracking.
Select All (*) Support
When users select all items in a table, the frontend sends ids: ['*']. The trait automatically applies table filters and search from the previous URL:
Comparison with WithTableQuery
| Feature | WithTableQuery (Form Request) | WithTableAction (CoreAction) |
|---|---|---|
| Used in | Form Requests | CoreActions |
| Input source | $this->input() | $this->params |
| Authorization | Manual in controller | Automatic via authorizeModel() |
| Validation | Manual rules() | Auto id/ids via idRules() |
| Query building | getTableQuery() | getTableQuery() |
Inline Edit Column Integration
Use CoreActions withInlineEditColumn for inline table editing:
See also: Inertia Table - InlineEditColumn
Known Issues & Limitations
Dynamic Action Properties Not Persisted
Properties set dynamically in controllers (e.g.,->withTrashed(), ->label()) are not available during action execution. Only properties defined in defaults() and params are persisted.
Workaround: Define all properties in the defaults() method or use conditional logic there for different contexts.
Best Practices
- Use Descriptive Names:
SendWelcomeEmailToNewUserActionnotEmailAction - Set Defaults in
defaults(): All metadata should be configured here - Implement
authorize(): This method is REQUIRED (abstract in base class) - always return a boolean - Validate Parameters: Always define validation rules for parameters
- Use Model Binding: Type-hint Eloquent models in
handle()- only IDs are sent to frontend, models auto-resolve - Use
getParam()for Authorization: Inauthorize(), use$this->getParam('key', Model::class)to auto-resolve models - Return Meaningful Messages: Use
->withSuccess()and->withError()for toast messages - Add Confirmation: Use for destructive actions
- Set Backend Params Explicitly: Use
->params(['key' => 'value'])with associative arrays to explicitly set parameter keys - Use getParams for UI State: For dynamic params based on frontend state
- Keep Actions Focused: One action should do one thing
- Pass Actions via ->with(): When you need to use actions without registering them in the AppBar
- Use
withTrashed()Explicitly: Only include soft-deleted models when necessary, matching route behavior - Hide Internal Actions: Use
hidden()for actions that should only be executed programmatically - Use
WithTableActionfor Table Operations: Simplifies single/bulk operations with automatic authorization and validation - Prefer
processModels()Over Manual Iteration: UseprocessModels()for cleaner code with automatic count tracking