coringawc / filament-action-approvals
Action-based approval flows for Filament with spatie/laravel-model-states integration.
Package info
github.com/CoringaWc/filament-action-approvals
pkg:composer/coringawc/filament-action-approvals
Requires
- php: ^8.3
- filament/filament: ^5.0
- spatie/laravel-package-tools: ^1.15
Requires (Dev)
- coringawc/filament-acl: ^1.0
- coringawc/filament-plugin-workbench: ^1.0
- larastan/larastan: ^3.0
- laravel/boost: ^2.4
- laravel/pint: ^1.0
- nunomaduro/collision: ^8.0
- pestphp/pest: ^4.6
- pestphp/pest-plugin-laravel: ^4.1
- phpunit/phpunit: ^12.0
- rector/rector: ^2.0
- spatie/laravel-model-states: ^2.11
- spatie/laravel-permission: ^7.0
Suggests
- coringawc/filament-acl: Enables HasResourcePermissions and PermissionSubject integration on ApprovalFlowResource (^1.0)
- spatie/laravel-model-states: Enables the HasStateApprovals and ResolvesPreviousState traits for Spatie state machines (^2.11)
- spatie/laravel-permission: Required for RoleResolver (^7.0)
README
Action-based approval workflows for Filament v5. Define multi-step approval flows with configurable approver resolvers, SLA enforcement, delegation, and lifecycle callbacks — all integrated into the Filament admin panel.
Features
- Multi-step approval flows — sequential steps with configurable approver resolution
- Polymorphic — any Eloquent model can be approvable via the
HasApprovalstrait - Pluggable approver resolvers —
UserResolver,RoleResolver,CustomRuleResolver, or create your own - Approvable actions — define domain-specific actions (submit, cancel, etc.) on your model, each with its own approval flow
- Per-action submit buttons — create dedicated
SubmitForApprovalActioninstances locked to a specificactionKey - Delegation — approvers can delegate their step to another user
- SLA enforcement — per-step SLA deadlines with warning notifications and configurable escalation (notify, auto-approve, reject, reassign)
- Lifecycle callbacks — hook into
onApprovalSubmitted,onApprovalApproved,onApprovalRejected, etc. directly on your model - Resubmission policy — control whether models can be resubmitted after approval/rejection
- User display name — uses Filament's
getFilamentName()when available, falls back tonameattribute - Built-in Filament components:
ApprovalFlowResource— CRUD for managing approval flow definitionsApprovalResource— operational approvals listing with status tabs, filters, overview stats, and slide-over detail viewListApprovalsAction— contextual action that opens a slide-over table for approvals scoped to a model or recordApprovalsDashboard— opt-in global dashboard with period filters and operational widgets
ApprovalsRelationManager— shows approval history with slide-over detailsApprovalStatusColumn— ready-to-use status badge columnApprovalStatusSection— infolist section with approval details and timeline- Grouped approval actions: Submit, Approve, Reject, Comment, Delegate (usable individually or through the
Aprovaçõesdropdown)
- Grouped approval actions: Submit, Approve, Reject, Comment, Delegate (usable individually or through the
- Widgets: Pending Approvals, Approval Analytics
- Multi-tenancy support — scope flows and approvers per tenant
- Event-driven — fires events for submitted, completed, rejected, step-completed, and escalated
- Notification system — notifies approvers, submitters, and escalation targets
- ACL integration — optional integration with
coringawc/filament-aclfor permission-aware resources
Requirements
- PHP 8.3+
- Laravel 12+
- Filament 5.x
Optional
coringawc/filament-acl^1.0 — enablesHasResourcePermissionsandPermissionSubjectintegrationspatie/laravel-permission^7.0 — required forRoleResolver
Installation
composer require coringawc/filament-action-approvals
Publish and run the migrations:
php artisan vendor:publish --tag="filament-action-approvals-migrations"
php artisan migrate
Optionally publish the config file:
php artisan vendor:publish --tag="filament-action-approvals-config"
Configuration
The config file (config/filament-action-approvals.php) contains:
return [ // The user model used for approver relationships 'user_model' => App\Models\User::class, // Registered resolver classes available in the flow builder 'approver_resolvers' => [ \CoringaWc\FilamentActionApprovals\ApproverResolvers\UserResolver::class, \CoringaWc\FilamentActionApprovals\ApproverResolvers\RoleResolver::class, \CoringaWc\FilamentActionApprovals\ApproverResolvers\CustomRuleResolver::class, ], // Multi-tenancy settings 'multi_tenancy' => [ 'enabled' => false, 'column' => 'company_id', 'scope_approvers' => true, ], // SLA warning threshold (0.75 = 75% of SLA time elapsed) 'sla_warning_threshold' => 0.75, // Date display settings 'date' => [ 'display_format' => 'd/m/Y H:i', 'use_since' => true, ], // Auto-register the SLA processing command to run every minute 'schedule_sla_command' => true, // Navigation group for the ApprovalFlow resource 'navigation_group' => null, // Toggle built-in operational actions in package UIs 'actions' => [ 'approve' => true, 'reject' => true, 'comment' => false, 'delegate' => false, ], // Broadcasting — opt-in per event (all disabled by default) 'broadcasting' => [ 'events' => [ 'submitted' => false, 'approved' => false, 'rejected' => false, 'cancelled' => false, 'commented' => false, 'delegated' => false, 'step_completed' => false, 'escalated' => false, ], 'queue' => null, ], // Super admin bypass — see and act on all approvals 'super_admin' => [ 'enabled' => false, 'roles' => ['super_admin'], 'user_ids' => [], ], // Resource customization 'resource' => [ 'enabled' => true, 'cluster' => null, 'navigation_sort' => null, 'navigation_icon' => null, 'show_widgets' => true, ], // ApprovalResource customization 'approvals_resource' => [ 'enabled' => true, 'navigation_sort' => null, 'navigation_icon' => null, ], // Opt-in operational dashboard 'dashboard' => [ 'enabled' => false, 'route_path' => 'approvals-dashboard', 'navigation_sort' => null, 'navigation_icon' => null, ], // Database table prefix (empty = no prefix) 'table_prefix' => '', ];
Plugin Registration
Register the plugin in your Filament panel provider:
use CoringaWc\FilamentActionApprovals\FilamentActionApprovalsPlugin; public function panel(Panel $panel): Panel { return $panel ->plugins([ FilamentActionApprovalsPlugin::make() ->flowResource() // Enable the ApprovalFlow CRUD resource (default: true) ->approvalResource() // Enable the operational ApprovalResource (default: true) ->dashboard() // Enable the ApprovalsDashboard page (default: false / opt-in) ->widgets() // Enable global panel widgets explicitly when also using the dedicated dashboard ->navigationGroup('Workflow'), // Override navigation group ]); }
Plugin Methods
| Method | Description |
|---|---|
flowResource(bool $enabled = true) |
Enable/disable the built-in ApprovalFlow resource |
approvalResource(bool $enabled = true) |
Enable/disable the built-in ApprovalResource |
dashboard(bool $enabled = true) |
Enable/disable the opt-in ApprovalsDashboard page |
widgets(bool $enabled = true) |
Enable/disable PendingApprovals and Analytics widgets |
resolvers(array $resolvers) |
Override approver resolvers for this panel |
userModel(string $model) |
Override the user model for this panel |
navigationGroup(string $group) |
Override the navigation group label |
If the dedicated ApprovalsDashboard page is enabled, global panel widgets are suppressed by default so the package dashboard stays independent from the panel's primary dashboard. Call ->widgets() explicitly if you want both.
Contextual approvals navigation
Use ListApprovalsAction when you want to open a slide-over table already scoped to a model type or a specific record:
use CoringaWc\FilamentActionApprovals\Actions\ListApprovalsAction; protected function getHeaderActions(): array { return [ ListApprovalsAction::make() ->forApprovableType(PurchaseOrderResource::getModel()), ]; }
For relation managers or nested contexts, pass the owner record instead:
ListApprovalsAction::make() ->forApprovable(fn (): Model => $this->getOwnerRecord())
The action opens a slide-over containing the approvals table already filtered to the relevant model or record. Use it on resource list pages or on headers of relation managers/nested resources that belong to an approvable model. Do not add it to ApprovalsRelationManager itself.
Dashboard opt-in
ApprovalsDashboard is disabled by default. Enable it explicitly when your panel needs a global operational view:
FilamentActionApprovalsPlugin::make() ->flowResource() ->approvalResource() ->dashboard()
The dashboard includes quick period actions (5d, 15d, 30d, All), an advanced filter slideover, status distribution, bottleneck visibility, stale pending approvals, and filtered analytics.
When this dedicated dashboard is enabled, the package no longer injects its global widgets into the panel's main dashboard unless you opt in with ->widgets().
Usage
1. Make Your Model Approvable
Add the HasApprovals trait to any Eloquent model:
use CoringaWc\FilamentActionApprovals\Concerns\HasApprovals; use CoringaWc\FilamentActionApprovals\Models\Approval; class PurchaseOrder extends Model { use HasApprovals; // React to approval lifecycle events public function onApprovalApproved(Approval $approval): void { $this->update(['status' => 'approved']); } public function onApprovalRejected(Approval $approval): void { $this->update(['status' => 'rejected']); } }
2. Define Approvable Actions (Optional)
If your model has multiple domain actions that require approval (e.g., submit, cancel, reimburse), define them via approvableActions():
class PurchaseOrder extends Model { use HasApprovals; /** * Define domain-specific actions that can be submitted for approval. * Each key is an action identifier, each value is a human-readable label. * * @return array<string, string> */ public static function approvableActions(): array { return [ 'submit' => __('Submit for Processing'), 'cancel' => __('Request Cancellation'), ]; } }
When approvableActions() is defined, the SubmitForApprovalAction will show a selector for the user to pick which action they are requesting. You can also create dedicated buttons per action — see Per-Action Submit Buttons.
3. Create an Approval Flow
Approval flows can be created via the admin panel UI (ApprovalFlow resource) or programmatically:
use CoringaWc\FilamentActionApprovals\ApproverResolvers\UserResolver; use CoringaWc\FilamentActionApprovals\ApproverResolvers\RoleResolver; use CoringaWc\FilamentActionApprovals\Enums\EscalationAction; use CoringaWc\FilamentActionApprovals\Enums\StepType; use CoringaWc\FilamentActionApprovals\Models\ApprovalFlow; $flow = ApprovalFlow::create([ 'name' => 'Purchase Order Approval', 'approvable_type' => PurchaseOrder::class, 'action_key' => 'submit', // Optional: tie this flow to a specific action 'is_active' => true, ]); // Step 1: Manager approval $flow->steps()->create([ 'name' => 'Manager Review', 'order' => 1, 'type' => StepType::Single, 'approver_resolver' => UserResolver::class, 'approver_config' => ['user_ids' => [1, 2]], 'required_approvals' => 1, 'sla_hours' => 24, 'escalation_action' => EscalationAction::Notify, ]); // Step 2: Director approval $flow->steps()->create([ 'name' => 'Director Sign-off', 'order' => 2, 'type' => StepType::Single, 'approver_resolver' => RoleResolver::class, 'approver_config' => ['role' => 'director'], 'required_approvals' => 1, 'sla_hours' => 48, 'escalation_action' => EscalationAction::AutoApprove, ]);
4. Add Approval Actions to Your Resource
Option A: All actions at once (quickstart)
Use the HasApprovalsResource trait to add the grouped approval menu as header actions:
use CoringaWc\FilamentActionApprovals\Concerns\HasApprovalsResource; class PurchaseOrderResource extends Resource { use HasApprovalsResource; // ... } // Edit or View Page class EditPurchaseOrder extends EditRecord { protected function getHeaderActions(): array { return [ ...static::getResource()::getApprovalHeaderActions(), // Returns: a single ActionGroup labeled "Aprovações" // containing SubmitForApprovalAction, ApproveAction, // RejectAction, CommentAction, and DelegateAction ]; } }
The default grouped trigger uses the vertical ellipsis icon and the Aprovações label so it can be reused consistently in resource headers and in approval detail infolists.
Option B: Individual actions (fine-grained control)
Use each action individually when you need to customize visibility, labels, or only show specific actions:
use CoringaWc\FilamentActionApprovals\Actions\ApproveAction; use CoringaWc\FilamentActionApprovals\Actions\CommentAction; use CoringaWc\FilamentActionApprovals\Actions\DelegateAction; use CoringaWc\FilamentActionApprovals\Actions\RejectAction; use CoringaWc\FilamentActionApprovals\Actions\SubmitForApprovalAction; class ViewInvoice extends ViewRecord { protected function getHeaderActions(): array { return [ // Custom domain actions first AdvanceStatusAction::make(), CancelAction::make(), // Only the approval response actions — submitter uses a different flow ApproveAction::make(), RejectAction::make(), CommentAction::make(), DelegateAction::make(), ]; } }
Each action manages its own visibility automatically:
| Action | Visible When |
|---|---|
SubmitForApprovalAction |
Record can be submitted for the current action context (no pending approval, flows exist, model policy allows it) |
ApproveAction |
User is an assigned approver and hasn't acted yet |
RejectAction |
User is an assigned approver and hasn't acted yet |
CommentAction |
A pending approval exists and user can act on it |
DelegateAction |
User is an assigned approver (original, not delegate) |
Per-Action Submit Buttons
When your model defines approvableActions(), you can create dedicated submit buttons for each action using actionKey():
use CoringaWc\FilamentActionApprovals\Actions\SubmitForApprovalAction; use Filament\Support\Icons\Heroicon; class EditPurchaseOrder extends EditRecord { protected function getHeaderActions(): array { return [ // Dedicated button for "submit" action — skips the action selector modal SubmitForApprovalAction::make('submitPO') ->actionKey('submit') ->label(__('Submit for Approval')) ->icon(Heroicon::OutlinedPaperAirplane), // Dedicated button for "cancel" action SubmitForApprovalAction::make('cancelPO') ->actionKey('cancel') ->label(__('Request Cancellation')) ->icon(Heroicon::OutlinedXMark) ->color('danger'), // Approval response actions ApproveAction::make(), RejectAction::make(), CommentAction::make(), DelegateAction::make(), ]; } }
When actionKey() is set:
- The action key selector modal is skipped entirely
- The action is only visible when there are matching flows for that specific
actionKey - If there's exactly one matching flow, submission happens with just a confirmation dialog (no form)
- If there are multiple matching flows, only the flow selector is shown (without the action selector)
This gives you full control to place specific approval submission buttons anywhere in your UI — in the header, in a custom action group, or even as table row actions.
5. Add the Relation Manager
Add the ApprovalsRelationManager to show approval history on any resource:
use CoringaWc\FilamentActionApprovals\RelationManagers\ApprovalsRelationManager; class PurchaseOrderResource extends Resource { public static function getRelations(): array { return [ ApprovalsRelationManager::class, ]; } }
The relation manager shows a table of all approvals for the record. Each row has a "View" action that opens a slide-over with:
- Approval details (flow, status, submitter, dates)
- Grouped approval actions at the top of the infolist when the current user can act
- Step-by-step progress with approver names and received/required counts
- Full audit trail (submitted, approved, rejected, commented, delegated, escalated)
6. Add the Status Column
Show the latest approval status in any table:
use CoringaWc\FilamentActionApprovals\Columns\ApprovalStatusColumn; class PurchaseOrderResource extends Resource { public static function table(Table $table): Table { return $table->columns([ TextColumn::make('title'), ApprovalStatusColumn::make(), // Badge: Pending, Approved, Rejected, Cancelled ]); } }
7. Add the Status Section to Infolists (Optional)
Use ApprovalStatusSection to display approval details inline on a View or Edit page. This is an alternative to the relation manager — useful when you want the approval details embedded directly in the page's infolist rather than in a separate tab or slide-over.
use CoringaWc\FilamentActionApprovals\Infolists\ApprovalStatusSection; class ViewPurchaseOrder extends ViewRecord { public function infolist(Infolist $infolist): Infolist { return $infolist->schema([ // ... your other infolist sections ... ApprovalStatusSection::make(), ]); } }
Note: When using
ApprovalsRelationManager, theApprovalStatusSectionmay be redundant since the RM's slide-over already shows full approval details.
8. Programmatic Usage
Interact with the approval engine directly:
use CoringaWc\FilamentActionApprovals\Services\ApprovalEngine; $engine = app(ApprovalEngine::class); // Submit for approval $approval = $engine->submit($purchaseOrder, $flow, auth()->id()); // Or via the model $approval = $purchaseOrder->submitForApproval($flow); // Approve a step $stepInstance = $approval->currentStepInstance(); $engine->approve($stepInstance, auth()->id(), 'Approved — budget ok'); // Reject $engine->reject($stepInstance, auth()->id(), 'Budget exceeded'); // Add a comment $engine->comment($approval, auth()->id(), 'Please provide receipts'); // Delegate to another user $engine->delegate($stepInstance, auth()->id(), $delegateUserId, 'On vacation'); // Cancel the approval $engine->cancel($approval);
User Display Names
The package uses UserDisplayName::resolve() to display user names throughout the UI (infolists, columns, selects, audit trail). The resolution order is:
- If the user model implements
Filament\Models\Contracts\HasName, callsgetFilamentName() - Falls back to the
nameattribute
This means if your User model uses Filament's HasName interface (which provides getFilamentName()), the package will automatically use the display name defined there (e.g., full name, username, or any custom format).
use Filament\Models\Contracts\FilamentUser; use Filament\Models\Contracts\HasName; class User extends Authenticatable implements FilamentUser, HasName { public function getFilamentName(): string { return "{$this->first_name} {$this->last_name}"; } }
Approver Resolvers
UserResolver
Resolves specific user IDs as approvers:
'approver_resolver' => UserResolver::class, 'approver_config' => ['user_ids' => [1, 2, 3]],
RoleResolver
Resolves all users with a given role (requires spatie/laravel-permission):
'approver_resolver' => RoleResolver::class, 'approver_config' => ['role' => 'manager'],
CustomRuleResolver
Define model-scoped custom rules directly on your model via approvalCustomRules():
class PurchaseOrder extends Model { use HasApprovals; public static function approvalCustomRules(): array { return [ 'department_head' => function (PurchaseOrder $model): array { return [$model->department->head_id]; }, 'finance_team' => function (PurchaseOrder $model): array { return User::where('department', 'finance')->pluck('id')->all(); }, ]; } }
Use in a step:
'approver_resolver' => CustomRuleResolver::class, 'approver_config' => ['custom_rule' => 'department_head'],
The resolver is only available in the flow builder when the selected model defines approvalCustomRules(). Each key becomes a selectable option.
Custom Resolver
Implement the ApproverResolver contract:
use CoringaWc\FilamentActionApprovals\Contracts\ApproverResolver; class HierarchyResolver implements ApproverResolver { public function resolve(array $config, Model $approvable): array { return [$approvable->user->manager_id]; } public static function label(): string { return 'Direct Manager'; } public static function configSchema(): array { return []; // Filament form components for the flow builder } public static function isAvailable(?string $modelClass = null): bool { return true; // Available for all models } }
Register it in the config or plugin:
// config 'approver_resolvers' => [ UserResolver::class, RoleResolver::class, HierarchyResolver::class, ], // or per-panel FilamentActionApprovalsPlugin::make() ->resolvers([UserResolver::class, HierarchyResolver::class])
Lifecycle Callbacks
Override these methods on your model to react to approval events:
| Method | Called When |
|---|---|
onApprovalSubmitted(Approval $approval) |
Model is submitted for approval |
onApprovalApproved(Approval $approval) |
All steps approved (approval complete) |
onApprovalRejected(Approval $approval) |
Approval rejected at any step |
onApprovalCancelled(Approval $approval) |
Approval is cancelled |
onApprovalCommented(ApprovalAction $action) |
Comment added |
onApprovalDelegated(ApprovalStepInstance $si, $from, $to) |
Step delegated |
onApprovalStepCompleted(ApprovalStepInstance $si) |
Individual step approved |
onApprovalEscalated(ApprovalStepInstance $si) |
SLA breach triggers escalation |
Resubmission Policy
Control whether a model can be resubmitted after a completed approval:
class Expense extends Model { use HasApprovals { canSubmitForApproval as protected canSubmitForApprovalThroughFlow; } // By default, approved and cancelled approvals cannot be resubmitted. // Override when your domain should also block or allow other outcomes. public function allowsApprovalResubmission(): bool { $latest = $this->latestApproval(); return ! $latest || $latest->status === ApprovalStatus::Rejected; } // Add project-specific submit rules on top of the default flow-approver policy public function canSubmitForApproval(?string $actionKey = null, int|string|null $userId = null): bool { return $this->user_id === ($userId ?? auth()->id()) || $this->canSubmitForApprovalThroughFlow($actionKey, $userId); } }
When you need different submit policies per approvable action, inspect the first parameter:
public function canSubmitForApproval(?string $actionKey = null, int|string|null $userId = null): bool { return match ($actionKey) { 'submit' => $this->user_id === ($userId ?? auth()->id()) || $this->canSubmitForApprovalThroughFlow($actionKey, $userId), 'cancel' => auth()->user()?->can('cancelPurchaseOrders') ?? false, default => false, }; }
If you do not override canSubmitForApproval(), the package default allows submission only for users resolved as approvers in a matching approval flow.
If you do not override allowsApprovalResubmission(), the package default blocks a new submission after the latest approval ends as approved or cancelled, while still allowing retries after rejected.
SLA Enforcement
Configure per-step SLA deadlines:
$flow->steps()->create([ 'name' => 'Urgent Review', 'sla_hours' => 4, 'escalation_action' => EscalationAction::AutoApprove, // ... ]);
Escalation Actions
| Action | Behavior |
|---|---|
Notify |
Sends an ApprovalEscalatedNotification |
AutoApprove |
Automatically approves the overdue step |
Reject |
Automatically rejects the approval |
Reassign |
Reassigns to new approvers using the step's resolver |
The SLA processor runs via the approval:process-sla command, automatically scheduled every minute when schedule_sla_command is true.
Events
| Event | Payload |
|---|---|
ApprovalSubmitted |
Approval $approval |
ApprovalCompleted |
Approval $approval |
ApprovalRejected |
Approval $approval |
ApprovalCancelled |
Approval $approval |
ApprovalCommented |
ApprovalAction $action |
ApprovalDelegated |
ApprovalStepInstance $stepInstance, int $fromUserId, int $toUserId |
ApprovalStepCompleted |
ApprovalStepInstance $stepInstance |
ApprovalEscalated |
ApprovalStepInstance $stepInstance |
Broadcasting
All events implement ShouldBroadcast but are disabled by default. Enable per-event broadcasting via config:
// config/filament-action-approvals.php 'broadcasting' => [ 'events' => [ 'submitted' => true, // Enable ApprovalSubmitted broadcasting 'approved' => true, // Enable ApprovalCompleted broadcasting 'rejected' => false, 'cancelled' => false, 'commented' => false, 'delegated' => false, 'step_completed' => false, 'escalated' => false, ], 'queue' => 'broadcasting', // null = default queue ],
Events broadcast on the approval-events channel. The BroadcastsConditionally trait provides broadcastWhen() that checks the per-event config — zero overhead when disabled.
Super Admin Bypass
When enabled, super admins can see and act on all approval actions (Approve, Reject, Comment, Delegate) regardless of being an assigned approver:
// config/filament-action-approvals.php 'super_admin' => [ 'enabled' => true, 'roles' => ['super_admin'], // spatie/laravel-permission roles 'user_ids' => [1], // explicit user IDs (always works) 'hide_from_selects' => true, // hide super admins from resolver selects ],
Check programmatically:
FilamentActionApprovalsPlugin::isSuperAdmin($userId);
When spatie/laravel-permission is not installed, only user_ids matching works. Role-based matching requires the package.
Enum-Based Approvable Actions
Instead of returning a plain array from approvableActions(), you can use a backed enum with HasLabel:
use Filament\Support\Contracts\HasColor; use Filament\Support\Contracts\HasIcon; use Filament\Support\Contracts\HasLabel; enum PurchaseOrderAction: string implements HasLabel, HasColor, HasIcon { case Submit = 'submit'; case Cancel = 'cancel'; public function getLabel(): string { return match ($this) { self::Submit => __('Submit for Processing'), self::Cancel => __('Request Cancellation'), }; } public function getColor(): string { return match ($this) { self::Submit => 'success', self::Cancel => 'danger', }; } public function getIcon(): string { return match ($this) { self::Submit => 'heroicon-o-paper-airplane', self::Cancel => 'heroicon-o-x-mark', }; } }
Then configure on your model using the #[ApprovableActions] attribute:
use CoringaWc\FilamentActionApprovals\Attributes\ApprovableActions; #[ApprovableActions(PurchaseOrderAction::class)] class PurchaseOrder extends Model { use HasApprovals; }
PHP 8.4 safety: The
#[ApprovableActions]attribute replaces the old$approvableActionsEnumproperty approach. PHP 8.4 fatals on trait property redefinition with different default values. Attributes are on the class, not the trait, avoiding this issue.
When an enum is configured, approvableActions() resolves labels from getLabel() automatically. You can also extract icon/color per action key:
use CoringaWc\FilamentActionApprovals\Support\ApprovableActionLabel; ApprovableActionLabel::iconFor($model, 'submit'); // 'heroicon-o-paper-airplane' ApprovableActionLabel::colorFor($model, 'submit'); // 'success'
Resource Customization
Customize the built-in ApprovalFlowResource appearance via config:
// config/filament-action-approvals.php 'resource' => [ 'enabled' => true, 'cluster' => \App\Filament\Admin\Clusters\Settings::class, 'navigation_sort' => 5, 'navigation_icon' => 'heroicon-o-cog', 'show_widgets' => true, // Show analytics widgets on the list page ],
Multi-Tenancy
Enable tenant-scoped approval flows:
// config/filament-action-approvals.php 'multi_tenancy' => [ 'enabled' => true, 'column' => 'company_id', // Your tenant foreign key 'scope_approvers' => true, // Also scope role-based resolvers ],
When enabled, ApprovalFlow::forModel() automatically scopes queries by the model's tenant column.
Database Schema
The package creates the following tables:
| Table | Purpose |
|---|---|
approval_flows |
Flow definitions (name, approvable_type, action_key, active status) |
approval_steps |
Step definitions within a flow (order, resolver, SLA, escalation) |
approvals |
Runtime approval instances (polymorphic to approvable model) |
approval_step_instances |
Runtime step instances (status, assigned approvers, SLA tracking) |
approval_actions |
Audit trail of all actions (submit, approve, reject, comment, delegate, escalate) |
approval_delegations |
Delegation records (from_user to_user per step instance) |
Integration with filament-acl (Optional)
If you use coringawc/filament-acl, you can extend the built-in ApprovalFlowResource to add permission-aware access control:
composer require coringawc/filament-acl
Create a custom resource that extends the package resource:
namespace App\Filament\Admin\Resources; use CoringaWc\FilamentAcl\Attributes\PermissionSubject; use CoringaWc\FilamentAcl\Resources\Concerns\HasResourcePermissions; use CoringaWc\FilamentActionApprovals\Resources\ApprovalFlows\ApprovalFlowResource as BaseResource; #[PermissionSubject('ApprovalFlow')] class ApprovalFlowResource extends BaseResource { use HasResourcePermissions; }
Then disable the built-in resource on the plugin and register your own:
FilamentActionApprovalsPlugin::make() ->flowResource(false) // Disable built-in resource
Translations
The package uses filament-action-approvals::approval.* keys for all user-facing strings. Translation files are in resources/lang/{locale}/approval.php.
Key groups:
| Group | Description |
|---|---|
status.* |
Approval status labels (pending, approved, etc.) |
action_type.* |
Action type labels (submitted, approved, etc.) |
step_type.* |
Step type labels (single, sequential, parallel) |
step_status.* |
Step instance status labels |
escalation.* |
Escalation action labels |
actions.* |
UI action button labels and messages |
To publish translations for customization:
php artisan vendor:publish --tag="filament-action-approvals-translations"
Testing
The package includes a workbench environment for development and testing:
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
License
The MIT License (MIT). Please see License File for more information.