PHPackages                             wezlo/filament-approval - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. wezlo/filament-approval

ActiveLibrary

wezlo/filament-approval
=======================

Approval Workflow Engine for Filament v5

1.0.0(yesterday)22↑2900%MITPHPPHP ^8.4

Since Apr 6Pushed yesterdayCompare

[ Source](https://github.com/mustafakhaleddev/filament-approval)[ Packagist](https://packagist.org/packages/wezlo/filament-approval)[ RSS](/packages/wezlo-filament-approval/feed)WikiDiscussions master Synced today

READMEChangelog (1)Dependencies (5)Versions (2)Used By (0)

Filament Approval Workflow Engine
=================================

[](#filament-approval-workflow-engine)

A configurable approval workflow package for Filament v5. Attach approval chains to any Eloquent model with single, sequential, or parallel approvers, SLA timers, escalation rules, delegation, and a full audit trail.

Requirements
------------

[](#requirements)

- PHP 8.4+
- Laravel 13+
- Filament 5+

Features
--------

[](#features)

- Polymorphic design -- attach approvals to any model via a trait
- Single, sequential, and parallel approval chains
- Configurable approval flows with a Filament resource UI
- Approve, reject, comment, and delegate actions
- SLA timers with auto-escalation (notify, auto-approve, reassign, reject)
- Delegation -- approvers can delegate to another user
- Full audit trail of every action
- Pluggable approver resolvers (specific users, roles, custom callbacks)
- Filament database notifications (requested, approved, rejected, escalated, SLA warning)
- Dashboard widgets (pending approvals table with clickable rows, analytics stats)
- Approval status badge column for resource tables
- Approvals relation manager (full history with slide-over detail view)
- Infolist section for current approval status at a glance
- Per-panel plugin configuration (user model, resolvers, navigation group)
- Scheduled command for SLA processing
- Publishable config and views

Installation
------------

[](#installation)

```
composer require wezlo/filament-approval
```

Publish and run migrations:

```
php artisan vendor:publish --tag=filament-approval-migrations
php artisan migrate
```

Ensure you have a `notifications` table (required for Filament database notifications):

```
php artisan make:notifications-table
php artisan migrate
```

Register the plugin in your Panel Provider:

```
use Wezlo\FilamentApproval\FilamentApprovalPlugin;

->plugins([
    FilamentApprovalPlugin::make(),
])
```

You can override resolvers, user model, and navigation group per-panel:

```
use Wezlo\FilamentApproval\FilamentApprovalPlugin;

// SuperAdmin panel -- uses Admin model and custom resolvers
->plugins([
    FilamentApprovalPlugin::make()
        ->userModel(\App\Models\Admin::class)
        ->resolvers([
            \App\ApproverResolvers\AdminResolver::class,
        ])
        ->navigationGroup('Admin Approvals'),
])

// Company panel -- uses defaults from config
->plugins([
    FilamentApprovalPlugin::make(),
])
```

Resolution order: **plugin override (per-panel)** &gt; **config file (global)** &gt; **default fallback**.

You can also disable the flow resource or widgets per-panel:

```
FilamentApprovalPlugin::make()
    ->flowResource(false)  // hide the Approval Flows resource
    ->widgets(false)       // hide dashboard widgets
```

Publish the config (optional):

```
php artisan vendor:publish --tag=filament-approval-config
```

Quick Start
-----------

[](#quick-start)

### 1. Add the trait to your model

[](#1-add-the-trait-to-your-model)

```
use Wezlo\FilamentApproval\Concerns\HasApprovals;

class PurchaseOrder extends Model
{
    use HasApprovals;
}
```

This gives you:

```
$order->submitForApproval();       // Submit using auto-detected flow
$order->submitForApproval($flow);  // Submit using a specific flow
$order->isPendingApproval();       // Check if pending
$order->isApproved();              // Check if approved
$order->isRejected();              // Check if rejected
$order->approvalStatus();          // Get ApprovalStatus enum
$order->latestApproval();          // Get latest Approval model
$order->currentApproval();         // Get current pending Approval
$order->approvals;                 // All approval instances
```

### 2. Add approval actions to your resource page

[](#2-add-approval-actions-to-your-resource-page)

```
use Wezlo\FilamentApproval\Concerns\HasApprovalsResource;

class ViewPurchaseOrder extends ViewRecord
{
    use HasApprovalsResource;

    protected static string $resource = PurchaseOrderResource::class;

    protected function getHeaderActions(): array
    {
        return [
            ...$this->getApprovalHeaderActions(),
            // your other actions...
        ];
    }
}
```

This adds five context-aware actions to the page header:

- **Submit for Approval** -- visible when no pending approval
- **Approve** -- visible to assigned/delegated approvers who haven't acted
- **Reject** -- same visibility, requires a comment
- **Comment** -- visible to assigned approvers during a pending approval
- **Delegate** -- visible to assigned approvers, lets them delegate to another user

### 3. Add the status column to your table

[](#3-add-the-status-column-to-your-table)

```
use Wezlo\FilamentApproval\Columns\ApprovalStatusColumn;

public static function table(Table $table): Table
{
    return $table->columns([
        TextColumn::make('title'),
        ApprovalStatusColumn::make(),
        // ...
    ]);
}
```

Displays a colored badge: Pending (warning), Approved (success), Rejected (danger), Cancelled (gray).

### 4. Add approval history to your resource

[](#4-add-approval-history-to-your-resource)

**Relation Manager** -- full approval history tab on View/Edit pages:

```
use Wezlo\FilamentApproval\RelationManagers\ApprovalsRelationManager;

public static function getRelations(): array
{
    return [
        ApprovalsRelationManager::class,
    ];
}
```

This adds an "Approvals" tab showing all approval instances. Clicking "View" on any row opens a slide-over with:

- Approval details (flow, status, submitter, dates)
- Step progress (each step with status, approvers, received/required count, SLA)
- Full audit trail (every action with who, what, when, comment)

**Infolist Section** -- current approval at a glance on View pages:

```
use Wezlo\FilamentApproval\Infolists\ApprovalStatusSection;

public static function infolist(Schema $schema): Schema
{
    return $schema->components([
        // your other entries...
        ApprovalStatusSection::make(),
    ]);
}
```

Shows a collapsible section with:

- Current status badge, flow name, submitter, dates
- Current step details (name, pending approvers, progress, SLA deadline with overdue highlighting)
- Recent activity timeline (collapsible)

The section auto-hides when there's no approval on the record.

### 5. Create an approval flow

[](#5-create-an-approval-flow)

Navigate to the **Approvals &gt; Approval Flows** resource in your Filament panel. Create a flow with:

- **Name** -- e.g. "Purchase Order Approval"
- **Applies To** -- dropdown auto-populated with models that use `HasApprovals` and are registered as resources in the current panel. Leave blank to apply to any model.
- **Steps** -- ordered list with:
    - Step name
    - Type: Single / Sequential / Parallel
    - Approver type: dropdown of resolvers configured on the plugin (e.g. Specific Users, Users by Role, or your custom resolvers)
    - Approver config: dynamic fields based on the selected resolver (user picker, role selector, etc.)
    - Required approvals (visible for Parallel type, with "Require N of M" hint)
    - SLA hours (optional)
    - Escalation action (visible when SLA is set)

Approval Flow Types
-------------------

[](#approval-flow-types)

### Single

[](#single)

One approver, one approval required. The simplest flow.

### Sequential

[](#sequential)

Multiple steps executed in order. Step 2 doesn't activate until step 1 is approved. A rejection at any step rejects the entire approval and skips remaining steps.

### Parallel

[](#parallel)

Multiple approvers on a single step. Configure `required_approvals` to set how many must approve (e.g., 2-of-3). The step completes when the threshold is met.

Approver Resolvers
------------------

[](#approver-resolvers)

Resolvers determine who can approve each step. Three are included:

### UserResolver

[](#userresolver)

Assigns specific users by ID:

```
Approver Type: Specific Users
Config: Select users from the dropdown

```

### RoleResolver

[](#roleresolver)

Assigns all users with a given Spatie role. Optionally scoped to the approvable model's `company_id`:

```
Approver Type: Users by Role
Config: Select a role

```

### CallbackResolver

[](#callbackresolver)

Register named callbacks in your service provider for custom logic:

```
use Wezlo\FilamentApproval\ApproverResolvers\CallbackResolver;

// In AppServiceProvider::boot()
CallbackResolver::register('project_manager', function ($approvable) {
    return [$approvable->project->manager_user_id];
});

CallbackResolver::register('department_head', function ($approvable) {
    return [$approvable->department->head_user_id];
});
```

Then select "Custom Callback" as the approver type in the flow builder.

### Custom Resolvers

[](#custom-resolvers)

Implement the `ApproverResolver` contract:

```
use Wezlo\FilamentApproval\Contracts\ApproverResolver;
use Illuminate\Database\Eloquent\Model;

class TeamLeadResolver implements ApproverResolver
{
    public function resolve(array $config, Model $approvable): array
    {
        return $approvable->team->leads->pluck('id')->all();
    }

    public static function label(): string
    {
        return 'Team Leads';
    }

    public static function configSchema(): array
    {
        return [
            // Filament form components for configuring this resolver
        ];
    }
}
```

Register it in `config/filament-approval.php`:

```
'approver_resolvers' => [
    \Wezlo\FilamentApproval\ApproverResolvers\UserResolver::class,
    \Wezlo\FilamentApproval\ApproverResolvers\RoleResolver::class,
    \Wezlo\FilamentApproval\ApproverResolvers\CallbackResolver::class,
    \App\ApproverResolvers\TeamLeadResolver::class,
],
```

SLA &amp; Escalation
--------------------

[](#sla--escalation)

Configure SLA on any step in the flow builder:

- **SLA (hours)** -- deadline after the step is activated
- **Escalation action** -- what happens when the SLA is breached:
    - **Send Reminder** -- notifies approvers again
    - **Auto-Approve** -- automatically approves the step
    - **Auto-Reject** -- automatically rejects the entire approval
    - **Reassign** -- reassigns to different approvers (configure via escalation config)

The `approval:process-sla` command runs every minute (configurable) and:

1. Sends SLA warnings at 75% of the deadline (configurable threshold)
2. Processes escalations when the deadline is breached

The command is auto-scheduled by the package. To disable:

```
// config/filament-approval.php
'schedule_sla_command' => false,
```

Delegation
----------

[](#delegation)

Any assigned approver can delegate their approval authority to another user. The delegate can then approve or reject on their behalf. Delegations are recorded in the audit trail.

Submission Policy
-----------------

[](#submission-policy)

By default, any authenticated user can submit any record for approval, and re-submission is allowed after approval or rejection. Override these methods on your model to customize:

### One-time approval (no re-submission)

[](#one-time-approval-no-re-submission)

```
class Contract extends Model
{
    use HasApprovals;

    /**
     * Once approved or rejected, the submit button won't appear again.
     */
    public function allowsApprovalResubmission(): bool
    {
        return false;
    }
}
```

### Restrict who can submit

[](#restrict-who-can-submit)

```
class PurchaseOrder extends Model
{
    use HasApprovals;

    /**
     * Only the creator or admins can submit for approval.
     */
    public function canSubmitForApproval(?int $userId = null): bool
    {
        $userId ??= auth()->id();

        return $this->created_by === $userId
            || User::find($userId)?->hasRole('admin');
    }
}
```

### Combine both

[](#combine-both)

```
class Invoice extends Model
{
    use HasApprovals;

    public function allowsApprovalResubmission(): bool
    {
        // Allow resubmission only if previously rejected (not if approved)
        $latest = $this->latestApproval();

        return ! $latest || $latest->status !== ApprovalStatus::Approved;
    }

    public function canSubmitForApproval(?int $userId = null): bool
    {
        return $this->created_by === ($userId ?? auth()->id());
    }
}
```

The `canBeSubmittedForApproval()` method combines all checks (pending status + resubmission policy + user authorization) and is used by the Submit action's visibility logic.

Audit Trail
-----------

[](#audit-trail)

Every action is recorded in the `approval_actions` table:

- Submitted
- Approved (with optional comment)
- Rejected (with required comment)
- Commented
- Delegated (with target user and reason)
- Escalated (with escalation action taken)

Access the audit trail:

```
$approval = $order->latestApproval();
$actions = $approval->actions; // Collection of ApprovalAction models
```

Notifications
-------------

[](#notifications)

The package sends Filament database notifications for:

- **Approval Requested** -- sent to each assigned approver when a step activates
- **Approval Approved** -- sent to the submitter when the full approval completes
- **Approval Rejected** -- sent to the submitter when rejected
- **SLA Warning** -- sent to approvers when approaching the deadline
- **Escalated** -- sent to approvers when the SLA is breached

Dashboard Widgets
-----------------

[](#dashboard-widgets)

The plugin registers two widgets:

### PendingApprovalsWidget

[](#pendingapprovalswidget)

A table showing the current user's pending approvals with step name, record reference, time waiting, and SLA status. Overdue items are highlighted in red.

### ApprovalAnalyticsWidget

[](#approvalanalyticswidget)

Stats overview with:

- Pending approvals count
- Approved in last 30 days
- Rejected in last 30 days
- Overdue steps count

Disable widgets:

```
FilamentApprovalPlugin::make()
    ->widgets(false)
```

Disable the flow resource:

```
FilamentApprovalPlugin::make()
    ->flowResource(false)
```

Blade Components
----------------

[](#blade-components)

The package includes Blade components for custom views:

```
{{-- Approval timeline --}}

{{-- Status badge --}}

{{-- Full approval history (pass the approvable record) --}}
@include('filament-approval::infolists.approval-history', ['record' => $order])
```

Publish views:

```
php artisan vendor:publish --tag=filament-approval-views
```

Events &amp; Model Callbacks
----------------------------

[](#events--model-callbacks)

There are two ways to react to approval lifecycle events: **Laravel events** (for decoupled listeners) and **model callbacks** (for logic that belongs on the model itself).

### Laravel Events

[](#laravel-events)

Listen to these events via event listeners or subscribers:

```
use Wezlo\FilamentApproval\Events\ApprovalSubmitted;
use Wezlo\FilamentApproval\Events\ApprovalStepCompleted;
use Wezlo\FilamentApproval\Events\ApprovalCompleted;
use Wezlo\FilamentApproval\Events\ApprovalRejected;
use Wezlo\FilamentApproval\Events\ApprovalEscalated;
```

Each event carries the relevant `Approval` or `ApprovalStepInstance` model.

### Model Lifecycle Callbacks

[](#model-lifecycle-callbacks)

Override these methods on any model using `HasApprovals` to react directly on the model:

```
use Wezlo\FilamentApproval\Concerns\HasApprovals;
use Wezlo\FilamentApproval\Models\Approval;
use Wezlo\FilamentApproval\Models\ApprovalAction;
use Wezlo\FilamentApproval\Models\ApprovalStepInstance;

class PurchaseOrder extends Model
{
    use HasApprovals;

    public function onApprovalSubmitted(Approval $approval): void
    {
        $this->update(['status' => 'pending_approval']);
    }

    public function onApprovalApproved(Approval $approval): void
    {
        $this->update(['status' => 'approved']);
        Mail::to($this->requester)->send(new OrderApprovedMail($this));
    }

    public function onApprovalRejected(Approval $approval): void
    {
        $this->update(['status' => 'rejected']);
    }

    public function onApprovalCancelled(Approval $approval): void
    {
        $this->update(['status' => 'draft']);
    }

    public function onApprovalCommented(ApprovalAction $action): void
    {
        // Notify the team about the comment
    }

    public function onApprovalDelegated(
        ApprovalStepInstance $stepInstance,
        int $fromUserId,
        int $toUserId,
    ): void {
        // Log delegation
    }

    public function onApprovalStepCompleted(ApprovalStepInstance $stepInstance): void
    {
        // Notify when a step passes
    }

    public function onApprovalEscalated(ApprovalStepInstance $stepInstance): void
    {
        // Alert management about SLA breach
    }
}
```

All callbacks are optional -- only override the ones you need. They are called after the action has been persisted to the database.

CallbackWhen it firesArguments`onApprovalSubmitted`Model submitted for approval`Approval``onApprovalApproved`All steps approved`Approval``onApprovalRejected`Rejected at any step`Approval``onApprovalCancelled`Approval cancelled`Approval``onApprovalCommented`Comment added`ApprovalAction``onApprovalDelegated`Approver delegates`ApprovalStepInstance`, `$fromUserId`, `$toUserId``onApprovalStepCompleted`Individual step approved`ApprovalStepInstance``onApprovalEscalated`SLA breached`ApprovalStepInstance`Programmatic Usage
------------------

[](#programmatic-usage)

Use the `ApprovalEngine` service directly:

```
use Wezlo\FilamentApproval\Services\ApprovalEngine;

$engine = app(ApprovalEngine::class);

// Submit
$approval = $engine->submit($order, $flow, auth()->id());

// Approve a step
$engine->approve($stepInstance, $userId, 'Looks good');

// Reject
$engine->reject($stepInstance, $userId, 'Budget exceeded');

// Comment
$engine->comment($approval, $userId, 'Please review section 3');

// Delegate
$engine->delegate($stepInstance, $fromUserId, $toUserId, 'On vacation');

// Cancel
$engine->cancel($approval);
```

Configuration
-------------

[](#configuration)

```
// config/filament-approval.php

return [
    'user_model' => \App\Models\User::class,

    'approver_resolvers' => [
        \Wezlo\FilamentApproval\ApproverResolvers\UserResolver::class,
        \Wezlo\FilamentApproval\ApproverResolvers\RoleResolver::class,
        \Wezlo\FilamentApproval\ApproverResolvers\CallbackResolver::class,
    ],

    'scope_approvers_to_company' => true,
    'sla_warning_threshold' => 0.75,    // 75% of SLA time
    'schedule_sla_command' => true,
    'navigation_group' => 'Approvals',
    'table_prefix' => '',
];
```

Translations
------------

[](#translations)

The package ships with **English** and **Arabic** translations. All UI strings (labels, messages, notifications, enum values) are fully translated.

Publish translations to customize:

```
php artisan vendor:publish --tag=filament-approval-translations
```

This copies the language files to `lang/vendor/filament-approval/`. The translation file is organized by section:

- `status.*` -- Approval statuses (Pending, Approved, Rejected, Cancelled)
- `step_type.*` -- Step types (Single, Sequential, Parallel)
- `action_type.*` -- Audit trail actions (Submitted, Approved, Delegated, etc.)
- `escalation.*` -- Escalation actions (Send Reminder, Auto-Approve, etc.)
- `flow.*` -- Flow builder form labels
- `actions.*` -- Approval action buttons and modals
- `notifications.*` -- Database notification titles and bodies
- `widgets.*` -- Dashboard widget labels
- `relation_manager.*` -- Relation manager labels
- `infolist.*` -- Infolist section labels

To add a new language, create `lang/vendor/filament-approval/{locale}/approval.php` with the same structure.

Custom Theme
------------

[](#custom-theme)

If you have a custom Filament theme, add the package views to your `@source` directive:

```
@source '../../../../vendor/wezlo/filament-approval/resources/views/**/*';
```

Testing
-------

[](#testing)

```
php artisan test --filter=ApprovalEngine
```

License
-------

[](#license)

MIT

###  Health Score

43

—

FairBetter than 90% of packages

Maintenance100

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

1d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/df745a14d08940e520abaf3b7cbbbaba551b14f0f73b530b9801cdafb75fabfa?d=identicon)[mustafakhaleddev](/maintainers/mustafakhaleddev)

---

Top Contributors

[![mustafakhaleddev](https://avatars.githubusercontent.com/u/25182746?v=4)](https://github.com/mustafakhaleddev "mustafakhaleddev (2 commits)")

---

Tags

laravelworkflowfilamentapproval

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/wezlo-filament-approval/health.svg)

```
[![Health](https://phpackages.com/badges/wezlo-filament-approval/health.svg)](https://phpackages.com/packages/wezlo-filament-approval)
```

###  Alternatives

[bezhansalleh/filament-shield

Filament support for `spatie/laravel-permission`.

2.8k2.9M88](/packages/bezhansalleh-filament-shield)[jibaymcs/filament-tour

Bring the power of DriverJs to your Filament panels and start a tour !

12247.8k](/packages/jibaymcs-filament-tour)[guava/filament-modal-relation-managers

Allows you to embed relation managers inside filament modals.

7565.0k4](/packages/guava-filament-modal-relation-managers)[mwguerra/filemanager

A full-featured file manager package for Laravel and Filament v5 with dual operating modes, drag-and-drop uploads, S3/MinIO support, and comprehensive security features.

718.5k1](/packages/mwguerra-filemanager)[agencetwogether/hookshelper

Simple plugin to toggle display hooks available in current page.

2312.7k](/packages/agencetwogether-hookshelper)[tapp/filament-webhook-client

Add a Filament resource and a policy for Spatie Webhook client

1120.2k](/packages/tapp-filament-webhook-client)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
