PHPackages                             azeem/approval-workflow - 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. [Database &amp; ORM](/categories/database)
4. /
5. azeem/approval-workflow

ActiveLibrary[Database &amp; ORM](/categories/database)

azeem/approval-workflow
=======================

A flexible approval workflow package for Laravel applications.

v1.0.7(2mo ago)0214↓72%MITPHPPHP ^8.1

Since Feb 28Pushed 2mo agoCompare

[ Source](https://github.com/azeemade/approval-worklfow)[ Packagist](https://packagist.org/packages/azeem/approval-workflow)[ RSS](/packages/azeem-approval-workflow/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (3)Versions (9)Used By (0)

approval-workflow
=================

[](#approval-workflow)

A flexible, configurable approval workflow package for Laravel. Attach multi-level approval processes to any Eloquent model with full audit logging, event-driven notifications, and team-based configuration.

---

Features
--------

[](#features)

- **Multi-level Approvals**: Define any number of sequential approval steps.
- **Team-based Workflows** (`team_id`): Run separate flows per team in multi-tenant apps.
- **Action-based Flows**: Distinct workflows per action type (e.g. `expense_report`, `onboarding`).
- **Configurable UUIDs**: Toggle between auto-increment integers or UUIDs as primary keys.
- **Request Metadata**: Pass arbitrary JSON data with a request (e.g. a frontend redirect URL).
- **Approver Tracking**: The model always knows who the `current_approver` is.
- **Approver Rerouting**: Reassign a pending request to a different approver mid-flow.
- **Action Timestamps**: Automatically captures `approved_at` and `rejected_at`.
- **Audit Logs**: Full history of every action with user, action type, and comment.
- **Event-Driven Notifications**: Fires `ApprovalRequested`, `RequestApproved`, `RequestRejected` events. Built-in listeners send notifications.
- **Flexible Notification Channels**: Configure `mail`, `database`, or any other Laravel channel.
- **Sync or Queued Notifications**: Toggle between sync (`sendNow`) and async (queued) dispatch.
- **Notification Themes**: Point to a custom Blade view for fully branded emails.

---

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

[](#installation)

```
composer require azeem/approval-workflow
```

Publish the config file and migrations:

```
php artisan vendor:publish --provider="Azeem\ApprovalWorkflow\ApprovalWorkflowServiceProvider"
```

Run the migrations:

```
php artisan migrate
```

---

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

[](#configuration)

All settings live in `config/approval-workflow.php`.

```
return [
    /**
     * Table names - override if you have naming conflicts.
     */
    'tables' => [
        'approval_flows'        => 'approval_flows',
        'approval_flow_steps'   => 'approval_flow_steps',
        'approval_requests'     => 'approval_requests',
        'approval_request_logs' => 'approval_request_logs',
    ],

    /**
     * The User model in your application.
     */
    'user_model' => App\Models\User::class,

    /**
     * Set to true to use UUIDs as primary/foreign keys in all workflow tables.
     * Note: requires re-publishing and re-running migrations.
     */
    'use_uuid' => false,

    /**
     * Notification settings.
     */
    'notifications' => [
        'enabled'   => true,

        // Laravel notification channels: 'mail', 'database', etc.
        'channels'  => ['mail'],

        // 'default' uses built-in MailMessage. Set to a Blade view path for custom themes.
        // e.g. 'emails.approvals.requested'
        'theme'     => 'default',

        // true = notifications are dispatched as queued jobs (recommended for production).
        // false = notifications are sent synchronously (useful for testing / simple setups).
        'use_queue' => true,
    ],
];
```

---

Usage
-----

[](#usage)

### 1. Define an Approval Flow

[](#1-define-an-approval-flow)

Create flows via seeders or an admin UI:

```
use Azeem\ApprovalWorkflow\Models\ApprovalFlow;

$flow = ApprovalFlow::create([
    'name'         => 'Expense Approval',
    'action_type'  => 'expense_report',
    'team_id'      => 1,       // null for non-tenant setups
    'is_active'    => true,
]);

$flow->steps()->create(['level' => 1, 'approver_id' => $managerId]);
$flow->steps()->create(['level' => 2, 'approver_id' => $financeId]);
```

### 2. Submit a Model for Approval

[](#2-submit-a-model-for-approval)

```
use Azeem\ApprovalWorkflow\Services\ApprovalService;

$service = app(ApprovalService::class);

$request = $service->submit($expense, [
    'action_type' => 'expense_report',
    'creator_id'  => auth()->id(),

    // Optional: metadata is stored as JSON and can include a redirect URL
    // that notifications will use as the "View Request" action link.
    'metadata' => [
        'redirect_url' => route('expenses.show', $expense->id),
        'amount'       => $expense->total,
    ],
]);
```

### 3. Approve a Request

[](#3-approve-a-request)

```
$service->approve($request, $approverUser, 'Looks good!');
```

The package automatically:

- Moves to the **next level** if more steps exist, notifying the next approver.
- Sets `status = approved` and records `approved_at` on the **final approval**.

### 4. Reject a Request

[](#4-reject-a-request)

```
$service->reject($request, $approverUser, 'Receipt is missing.');
```

Sets `status = rejected`, records `rejected_at`, and notifies the creator.

### 5. Reroute a Request

[](#5-reroute-a-request)

Replace the current approver for any pending request:

```
$service->reroute($request, $newApproverId, $adminUser);
```

This updates `current_approver_id`, logs the action, and sends a new notification to the replacement approver.

### 6. Request Changes (Return to Creator)

[](#6-request-changes-return-to-creator)

Instead of outright rejecting a request, an approver can ask the creator to modify their submission.

```
$service->requestChanges($request, $approverUser, 'Please attach the missing receipt', ['receipt_file']);
```

This sets `status = returned` and fires a `ChangesRequestedNotification` to the creator. The optional 4th parameter `['receipt_file']` allows you to store the specific fields that need to be changed in a `requested_changes` JSON column, which your frontend can use to highlight errors.

### 7. Remove an Approver

[](#7-remove-an-approver)

You can dynamically remove a specific approver from a request without removing them entirely from the workflow template.

```
$service->removeApprover($request, $approverIdToRemove, $adminUser);
```

If the removed approver is currently the active approver holding up the request, the package will automatically auto-advance the request to the next level. If it was the final level, it will auto-approve.

### 8. Conditional Workflows

[](#8-conditional-workflows)

You can configure an approval flow to run only if certain conditions are met (e.g., amount &gt; $5000, or specifically tailored business rules). If the condition fails, the request is instantly created as `status = skipped` and processing continues immediately.

**Step 1:** Create a condition class implementing `ApprovalCondition`:

```
use Azeem\ApprovalWorkflow\Contracts\ApprovalCondition;
use Illuminate\Database\Eloquent\Model;

class HighAmountCondition implements ApprovalCondition
{
    public function requiresApproval(Model $model, array $attributes): bool
    {
        return $attributes['amount'] > 5000;
    }
}
```

**Step 2:** Attach it to your workflow flow when creating it:

```
$flow = ApprovalFlow::create([
    'name' => 'High Value Expense Approval',
    'action_type' => 'expense_report',
    'condition_class' => HighAmountCondition::class,
    'is_active' => true,
]);
```

When you call `$service->submit(...)`, your condition is automatically evaluated!

### 9. Multi-Approver Levels (Maker &amp; Checker)

[](#9-multi-approver-levels-maker--checker)

You can assign multiple users to a single level (like a group of "Makers" or "Checkers") and configure whether **any** one person can approve, or if **all** of them must approve.

```
// Any Strategy (The first person to approve advances the request)
$flow->steps()->create([
    'level' => 1,
    'approvers' => [10, 11, 12],
    'strategy' => 'any'
]);

// All Strategy (Everyone in the array must approve before it advances)
$flow->steps()->create([
    'level' => 2,
    'approvers' => [20, 21],
    'strategy' => 'all'
]);
```

The system automatically tracks who has approved via the `pending_approvers` and `approved_by` JSON arrays on the `ApprovalRequest` model, and sends bulk notifications correctly to everyone pending.

---

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

[](#notifications)

Notifications are dispatched automatically via events. The built-in `SendApprovalNotifications` listener handles all three events.

EventWho is notified`ApprovalRequested`The current approver (`current_approver_id`)`RequestApproved`The request creator`RequestRejected`The request creator### Custom Themes

[](#custom-themes)

Set `'theme'` to a Blade view path to fully control email content:

```
// config/approval-workflow.php
'notifications' => [
    'theme' => 'emails.approvals.custom',
],
```

Your view receives `$request` (the `ApprovalRequest` model) and `$notifiable` (the user).

### Adding Custom Channels (e.g. Database, Firebase)

[](#adding-custom-channels-eg-database-firebase)

Add channels to the `channels` array. Any standard Laravel notification channel is supported out of the box:

```
'channels' => ['mail', 'database'],
```

For third-party channels (e.g. `firebase`, `slack`), install the corresponding package and add its channel driver name:

```
'channels' => ['mail', 'fcm'],
```

Then extend the notification classes to add `toFcm()` or `toSlack()` methods, or publish and override them.

### Sync vs Queued Dispatch

[](#sync-vs-queued-dispatch)

```
// Async (default, recommended for production - uses Laravel Queue)
'use_queue' => true,

// Sync (fires immediately in the same request cycle)
'use_queue' => false,
```

---

Events
------

[](#events)

You can listen to these events in your own application to add custom business logic:

```
// In your EventServiceProvider
use Azeem\ApprovalWorkflow\Events\ApprovalRequested;
use Azeem\ApprovalWorkflow\Events\RequestApproved;
use Azeem\ApprovalWorkflow\Events\RequestRejected;

Event::listen(RequestApproved::class, function ($event) {
    $expense = $event->request->model;
    $expense->update(['approved' => true]);
});
```

---

Recommendations
---------------

[](#recommendations)

These features are planned or recommended for future versions:

1. **Role-based Approvers**: Assign steps to roles (e.g. via Spatie Permissions) instead of specific user IDs.
2. **Conditional Step Skipping**: Skip steps based on request attributes (e.g. skip finance step if amount &lt; $50).
3. **Due Dates &amp; Reminders**: Track `due_at` and send reminder notifications via a scheduled job.
4. **Callback on Final Approval**: Register a closure/invokable class in config to run after final approval (e.g. update the model).
5. **Drag-and-Drop Flow Builder UI**: A first-party Filament or Nova resource for managing flows visually.
6. **Model-driven Flow Trigger**: Automatically submit a model on save via a trait, configured via `trigger_type = 'model_save'`.
7. **Audit Trail UI Component**: A Blade/Livewire component to render the approval log timeline.

---

License
-------

[](#license)

MIT

###  Health Score

43

—

FairBetter than 90% of packages

Maintenance92

Actively maintained with recent releases

Popularity16

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity47

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

Every ~0 days

Total

8

Last Release

69d ago

### Community

Maintainers

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

---

Top Contributors

[![azeemade](https://avatars.githubusercontent.com/u/59115414?v=4)](https://github.com/azeemade "azeemade (12 commits)")

---

Tags

laravelmodeleloquentworkflowapproval

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/azeem-approval-workflow/health.svg)

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

###  Alternatives

[mongodb/laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel

7.1k7.2M70](/packages/mongodb-laravel-mongodb)[tucker-eric/eloquentfilter

An Eloquent way to filter Eloquent Models

1.8k4.8M26](/packages/tucker-eric-eloquentfilter)[dyrynda/laravel-model-uuid

This package allows you to easily work with UUIDs in your Laravel models.

4802.8M8](/packages/dyrynda-laravel-model-uuid)[spiritix/lada-cache

A Redis based, automated and scalable database caching layer for Laravel

591444.8k2](/packages/spiritix-lada-cache)[pdphilip/elasticsearch

An Elasticsearch implementation of Laravel's Eloquent ORM

145360.2k4](/packages/pdphilip-elasticsearch)[highsolutions/eloquent-sequence

A Laravel package for easy creation and management sequence support for Eloquent models with elastic configuration.

121130.3k](/packages/highsolutions-eloquent-sequence)

PHPackages © 2026

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