coringawc/filament-action-approvals

Action-based approval flows for Filament with spatie/laravel-model-states integration.

Maintainers

Package info

github.com/CoringaWc/filament-action-approvals

pkg:composer/coringawc/filament-action-approvals

Statistics

Installs: 13

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v2.3.6 2026-04-23 07:12 UTC

This package is auto-updated.

Last update: 2026-04-23 07:12:48 UTC


README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

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 HasApprovals trait
  • Pluggable approver resolversUserResolver, 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 SubmitForApprovalAction instances locked to a specific actionKey
  • 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 to name attribute
  • Built-in Filament components:
    • ApprovalFlowResource — CRUD for managing approval flow definitions
      • ApprovalResource — operational approvals listing with status tabs, filters, overview stats, and slide-over detail view
      • ListApprovalsAction — contextual action that opens a slide-over table for approvals scoped to a model or record
      • ApprovalsDashboard — opt-in global dashboard with period filters and operational widgets
    • ApprovalsRelationManager — shows approval history with slide-over details
    • ApprovalStatusColumn — ready-to-use status badge column
    • ApprovalStatusSection — infolist section with approval details and timeline
      • Grouped approval actions: Submit, Approve, Reject, Comment, Delegate (usable individually or through the Aprovações dropdown)
    • 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-acl for permission-aware resources

Requirements

  • PHP 8.3+
  • Laravel 12+
  • Filament 5.x

Optional

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, the ApprovalStatusSection may 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:

  1. If the user model implements Filament\Models\Contracts\HasName, calls getFilamentName()
  2. Falls back to the name attribute

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 $approvableActionsEnum property 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.