PHPackages                             moffhub/maker-checker - 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. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. moffhub/maker-checker

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

moffhub/maker-checker
=====================

Enterprise-grade maker-checker (four-eyes) approval workflow for Laravel. Multi-role approvals, conditional rules engine, delegation, audit trail, bulk actions, and auto-intercept via Eloquent events.

v0.1.0(3mo ago)08.8k↓50%MITPHPPHP ^8.3 || ^8.4 || ^8.5

Since Aug 28Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/Moffhub-Solutions/maker-checker)[ Packagist](https://packagist.org/packages/moffhub/maker-checker)[ RSS](/packages/moffhub-maker-checker/feed)WikiDiscussions develop Synced 1mo ago

READMEChangelog (10)Dependencies (8)Versions (17)Used By (0)

Maker-Checker for Laravel
=========================

[](#maker-checker-for-laravel)

[![Latest Version on Packagist](https://camo.githubusercontent.com/2e7b7f6052d4363cb2d001c8c609b23e60574608b3b7329ce79c3156d7ba98ef/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d6f66666875622f6d616b65722d636865636b65722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/moffhub/maker-checker)[![Total Downloads](https://camo.githubusercontent.com/8e5ed86b070ce10e7b908425336570b825bd0e0fc934ad66f0b4e9c527283478/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6d6f66666875622f6d616b65722d636865636b65722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/moffhub/maker-checker)[![License](https://camo.githubusercontent.com/90d7f979fc5b9ac6c4f7ed866b0def2177da0827c60969a7155e4fd60fb503e4/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6d6f66666875622f6d616b65722d636865636b65722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/moffhub/maker-checker)[![PHP Version](https://camo.githubusercontent.com/ac066b32ddbc429eb58e1450657c06d26d34a3b028ba8589276fa37b2a4d0a72/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6d6f66666875622f6d616b65722d636865636b65722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/moffhub/maker-checker)

The most feature-complete **maker-checker (four-eyes principle)** approval workflow package for Laravel. Add multi-level approval requirements to any Eloquent model with a single trait, or use the full-featured API for complex enterprise workflows.

Unlike simpler approval packages, this supports **multi-role approvals**, a **conditional rules engine**, **approval delegation**, **bulk operations**, **audit trail with export**, **reminders &amp; escalation**, and **auto-intercept via Eloquent model events** -- all out of the box.

Why This Package?
-----------------

[](#why-this-package)

Featuremoffhub/maker-checkerOthersAuto-intercept via trait✅SomeMulti-role approvals (2 admins + 1 manager)✅RareUser-specific approvals✅❌Conditional rules engine (if amount &gt; 50K...)✅❌Database-driven config (change rules at runtime)✅❌Execute arbitrary actions (not just CRUD)✅❌Approval delegation with expiry✅❌Bulk approve endpoint✅❌Reminders &amp; escalation✅❌Audit trail + CSV/JSON export✅RareRace condition safe (pessimistic locking)✅❌REST API included✅❌360+ tests✅VariesFeatures
--------

[](#features)

- **Auto-intercept** - Add `RequiresApproval` trait to any model; create/update/delete are intercepted automatically
- **Multi-role approvals** - Require approvals from specific roles (e.g., 2 admins + 1 manager)
- **User-specific approvals** - Require specific people to approve by email or ID
- **Conditional rules engine** - Different approval rules based on payload (e.g., amount &gt; 50,000 needs extra approval)
- **CRUD + Execute** - Built-in support for Create, Update, Delete, and custom Execute operations
- **Flexible configuration** - Configure via file, database, model interfaces, or runtime API
- **Multi-tenancy support** - Team/company scoping for requests
- **API ready** - RESTful API endpoints for managing requests, configs, delegations, and audits
- **Bulk operations** - Approve multiple requests in one call
- **Delegation** - Delegate approval authority to another user with optional expiry
- **Hooks &amp; callbacks** - Execute custom logic before/after approval or rejection
- **Reminders &amp; escalation** - Auto-remind approvers; escalate after configurable delay
- **Request expiration** - Auto-expire pending requests after a configurable time
- **Audit trail** - Full audit log with CSV/JSON export endpoint
- **Notifications** - Built-in email/database notifications with sequential approval support
- **Race condition safe** - Pessimistic locking prevents double-approval bugs

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

[](#installation)

Install the package via Composer:

```
composer require moffhub/maker-checker
```

Publish and run the migrations:

```
php artisan vendor:publish --tag=maker-checker-migrations
php artisan migrate
```

Optionally publish the config file:

```
php artisan vendor:publish --tag=maker-checker-config
```

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

[](#quick-start)

### Option A: Automatic Model Event Interception (Recommended)

[](#option-a-automatic-model-event-interception-recommended)

The simplest way to add maker-checker to your models is using the `RequiresApproval` trait. This automatically intercepts create, update, and delete operations:

```
use Illuminate\Database\Eloquent\Model;
use Moffhub\MakerChecker\Traits\RequiresApproval;

class Post extends Model
{
    use RequiresApproval;

    // Optionally specify which actions require approval
    protected static array $requiresApprovalFor = ['create', 'delete'];

    // Optionally define approval requirements
    protected static array $approvalRequirements = [
        'create' => ['editor' => 1],
        'delete' => ['admin' => 2],
    ];
}
```

Now when you try to create or delete a Post, the operation returns `false` and a pending approval request is created:

```
$post = new Post(['title' => 'My Post', 'user_id' => auth()->id()]);
$saved = $post->save();

if (!$saved && Post::wasIntercepted()) {
    $request = Post::getInterceptedRequest();

    return response()->json([
        'message' => 'Your request has been submitted for approval.',
        'request_id' => $request->id,
        'request_code' => $request->code,
    ], 202); // HTTP 202 Accepted
}

// To bypass approval (for admin operations, seeders, etc.):
$post = Post::createWithoutApproval(['title' => 'Direct Create']);

// Or use the callback method:
Post::withoutApprovalDo(function () {
    Post::create(['title' => 'Also bypassed']);
});
```

### Option B: Convenience Methods (Simple API)

[](#option-b-convenience-methods-simple-api)

Use the `MakerChecker` facade with convenience methods that auto-inject the authenticated user:

```
use Moffhub\MakerChecker\Facades\MakerChecker;

// Create a request (auto-injects auth user as maker)
$request = MakerChecker::create(Post::class, ['title' => 'My Post']);

// With custom description
$request = MakerChecker::create(Post::class, ['title' => 'My Post'], 'Create a new blog post');

// Update a model
$request = MakerChecker::update($post, ['title' => 'Updated Title']);

// Delete a model
$request = MakerChecker::delete($post);

// Execute a custom action
$request = MakerChecker::execute(TransferFunds::class, ['amount' => 5000]);
```

Approve, reject, or cancel requests (also auto-injects auth user):

```
// Approve (uses authenticated user)
MakerChecker::approve($request);
MakerChecker::approve($request, null, 'admin');           // With role
MakerChecker::approve($request, null, 'admin', 'LGTM');  // With role and remarks

// Or with explicit user
MakerChecker::approve($request, $approver, 'admin');

// Reject
MakerChecker::reject($request);
MakerChecker::reject($request, null, 'Missing information');

// Cancel (only maker can cancel)
MakerChecker::cancel($request);
```

You can also call these methods directly on the request model:

```
$request->approve();                          // Uses auth user
$request->approve(null, 'admin');             // With role
$request->approve($user, 'admin', 'Approved'); // Explicit user

$request->reject(null, 'Not approved');
$request->cancel();
```

### Option C: Request Builder (Full Control)

[](#option-c-request-builder-full-control)

For advanced usage with hooks and custom configuration:

```
use Moffhub\MakerChecker\Facades\MakerChecker;

$request = MakerChecker::request()
    ->toCreate(Post::class, ['title' => 'My Post'])
    ->madeBy(auth()->user())
    ->description('Create a new blog post')
    ->withApprovals(['editor' => 1, 'admin' => 1])
    ->beforeApproval(fn($r) => Log::info('Approving...'))
    ->afterApproval(fn($r) => Notification::send(...))
    ->save();
```

### 1. Implement the User Contract

[](#1-implement-the-user-contract)

Add the `MakerCheckerUserContract` interface to your User model:

```
use Moffhub\MakerChecker\Contracts\MakerCheckerUserContract;

class User extends Authenticatable implements MakerCheckerUserContract
{
    public function hasMakerCheckerPermission(string $permission): bool
    {
        return $this->hasPermission($permission); // Your permission logic
    }

    public function getMakerCheckerTeamId(): ?int
    {
        return $this->team_id; // For multi-tenancy, or null
    }

    public function getMakerCheckerRole(): ?string
    {
        return $this->role; // e.g., 'admin', 'manager'
    }

    public function getMakerCheckerEmail(): ?string
    {
        return $this->email;
    }
}
```

### 2. Create a Pending Request

[](#2-create-a-pending-request)

Use the `MakerChecker` facade to create approval requests:

```
use Moffhub\MakerChecker\Facades\MakerChecker;
use App\Models\Post;

// Create request for a new Post
$request = MakerChecker::request()
    ->toCreate(Post::class, [
        'title' => 'My New Post',
        'content' => 'Post content here...',
        'user_id' => auth()->id(),
    ])
    ->madeBy(auth()->user())
    ->description('Create a new blog post')
    ->save();
```

### 3. Approve or Reject

[](#3-approve-or-reject)

```
use Moffhub\MakerChecker\Facades\MakerChecker;

// Simple - uses authenticated user automatically
MakerChecker::approve($request);
MakerChecker::reject($request, null, 'Missing required information');

// With role (for multi-role approvals)
MakerChecker::approve($request, null, 'admin', 'Looks good!');

// With explicit user
MakerChecker::approve($request, $approverUser, 'admin', 'Looks good!');

// Or call directly on the request model
$request->approve();
$request->approve(null, 'admin');
$request->reject(null, 'Not approved');
$request->cancel(); // Only maker can cancel
```

Request Types
-------------

[](#request-types)

### Create

[](#create)

```
MakerChecker::request()
    ->toCreate(Post::class, ['title' => 'New Post', 'content' => '...'])
    ->withApprovals(['admin' => 1])
    ->madeBy(auth()->user())
    ->save();
```

### Update

[](#update)

```
MakerChecker::request()
    ->toUpdate($post, ['title' => 'Updated Title'])
    ->madeBy(auth()->user())
    ->save();
```

### Delete

[](#delete)

```
MakerChecker::request()
    ->toDelete($post)
    ->withApprovals(['admin' => 2]) // Require 2 admin approvals
    ->madeBy(auth()->user())
    ->save();
```

### Execute (Custom Actions)

[](#execute-custom-actions)

For complex operations, create an executable class:

```
use Moffhub\MakerChecker\Contracts\ExecutableRequest;
use Moffhub\MakerChecker\Models\MakerCheckerRequest;

class TransferFunds extends ExecutableRequest
{
    public function execute(MakerCheckerRequest $request): void
    {
        $payload = $request->payload;

        // Perform the transfer
        BankService::transfer(
            from: $payload['from_account'],
            to: $payload['to_account'],
            amount: $payload['amount']
        );
    }

    public function uniqueBy(): array
    {
        return ['from_account', 'to_account', 'amount'];
    }

    public function beforeApproval(MakerCheckerRequest $request): void
    {
        // Validate accounts still exist
    }

    public function afterApproval(MakerCheckerRequest $request): void
    {
        // Send notification
    }

    public function onFailure(MakerCheckerRequest $request): void
    {
        // Handle failure
    }
}
```

Then create the request:

```
MakerChecker::request()
    ->toExecute(TransferFunds::class, [
        'from_account' => 'ACC001',
        'to_account' => 'ACC002',
        'amount' => 5000,
    ])
    ->withApprovals(['finance' => 1, 'manager' => 1])
    ->madeBy(auth()->user())
    ->save();
```

Multi-Role Approvals
--------------------

[](#multi-role-approvals)

Require approvals from multiple roles:

```
MakerChecker::request()
    ->toCreate(User::class, $userData)
    ->withApprovals([
        'hr' => 1,        // 1 HR approval
        'admin' => 2,     // 2 Admin approvals
        'manager' => 1,   // 1 Manager approval
    ])
    ->madeBy(auth()->user())
    ->save();
```

Check approval status:

```
$request->getApprovalCount();        // Total approvals received
$request->getPendingRoles();         // ['admin' => 1, ...] remaining
$request->hasMetApprovalThreshold(); // true/false
```

User-Specific Approvals
-----------------------

[](#user-specific-approvals)

In addition to role-based approvals, you can require specific users to approve a request. This is useful when you need approval from a particular person regardless of their role.

### Requiring Specific Users

[](#requiring-specific-users)

Specify users by email or ID:

```
// Require approval from a specific user by email
MakerChecker::request()
    ->toCreate(Contract::class, $data)
    ->requiringUsersToApprove(['cfo@company.com'])
    ->madeBy(auth()->user())
    ->save();

// Require approval from multiple specific users
MakerChecker::request()
    ->toCreate(Contract::class, $data)
    ->requiringUsersToApprove(['cfo@company.com', 'ceo@company.com'])
    ->madeBy(auth()->user())
    ->save();

// Require approval from user by ID
MakerChecker::request()
    ->toCreate(Contract::class, $data)
    ->requiringUsersToApprove([(string) $cfoUser->id])
    ->madeBy(auth()->user())
    ->save();
```

### Combining Roles and Users

[](#combining-roles-and-users)

Require both role-based and user-specific approvals:

```
// Requires 1 admin approval AND approval from the CFO
MakerChecker::request()
    ->toCreate(Contract::class, $data)
    ->withRoleAndUserApprovals(
        roles: ['admin' => 1],
        users: ['cfo@company.com']
    )
    ->madeBy(auth()->user())
    ->save();
```

### User Validation

[](#user-validation)

By default, the package validates that all specified users exist in the system before creating the request:

```
// This will throw an exception if the user doesn't exist
MakerChecker::request()
    ->toCreate(Contract::class, $data)
    ->requiringUsersToApprove(['nonexistent@company.com'])
    ->madeBy(auth()->user())
    ->save();
// Throws: RequestCouldNotBeInitiated

// Disable validation if needed (not recommended)
MakerChecker::request()
    ->toCreate(Contract::class, $data)
    ->requiringUsersToApprove(['future@company.com'], validateExistence: false)
    ->madeBy(auth()->user())
    ->save();
```

### Checking Pending Users

[](#checking-pending-users)

```
$request->requiresUserApprovals();   // true if users are required
$request->getPendingUsers();         // ['cfo@company.com', ...] remaining
```

### Approval Flow

[](#approval-flow)

When a user-specific approval is required, only the specified users can approve:

```
$request = MakerChecker::request()
    ->toCreate(Contract::class, $data)
    ->requiringUsersToApprove(['cfo@company.com'])
    ->madeBy(auth()->user())
    ->save();

// Only the CFO can approve - other users will get an error
MakerChecker::approve($request, $cfoUser, 'user'); // Works
MakerChecker::approve($request, $otherUser);       // Throws exception
```

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

[](#configuration)

### Model-Based Configuration

[](#model-based-configuration)

Implement `MakerCheckerConfigurable` on your models:

```
use Moffhub\MakerChecker\Contracts\MakerCheckerConfigurable;
use Moffhub\MakerChecker\Enums\RequestType;

class Post extends Model implements MakerCheckerConfigurable
{
    public static function makerCheckerApprovals(): array
    {
        return [
            'create' => ['editor' => 1],
            'update' => ['editor' => 1],
            'delete' => ['admin' => 1, 'editor' => 1],
        ];
    }

    public static function makerCheckerUniqueFields(): array
    {
        return [
            'create' => ['title', 'slug'],
        ];
    }

    public static function requiresMakerChecker(RequestType $action): bool
    {
        // Only require approval for delete
        return $action === RequestType::DELETE;
    }

    public static function makerCheckerDescription(RequestType $action, array $payload): string
    {
        return match($action) {
            RequestType::CREATE => "Create post: {$payload['title']}",
            RequestType::UPDATE => "Update post",
            RequestType::DELETE => "Delete post",
            default => "Post operation",
        };
    }
}
```

### File-Based Configuration

[](#file-based-configuration)

Configure in `config/maker-checker.php`:

```
'models' => [
    App\Models\User::class => [
        'approvals' => [
            'create' => ['hr' => 1, 'admin' => 1],
            'update' => ['admin' => 1],
            'delete' => ['admin' => 2],
        ],
        'unique_fields' => [
            'create' => ['email'],
        ],
        'required_for' => ['create', 'delete'],
    ],
],

'executables' => [
    App\MakerChecker\TransferFunds::class => [
        'approvals' => ['finance' => 1, 'manager' => 1],
        'unique_fields' => ['from_account', 'to_account', 'amount'],
    ],
],

'global_approvals' => [
    'create' => ['admin' => 1],
    'update' => ['admin' => 1],
    'delete' => ['admin' => 2],
    'execute' => ['admin' => 1],
],
```

### Database-Driven Configuration

[](#database-driven-configuration)

Enable the database driver for dynamic configuration:

```
// config/maker-checker.php
'config_driver' => 'database',
```

Manage configs via API or programmatically:

```
use Moffhub\MakerChecker\Models\MakerCheckerConfig;

// Create with role-based approvals
MakerCheckerConfig::create([
    'configurable_type' => Post::class,
    'action' => 'delete',
    'approvals' => [
        'roles' => ['admin' => 2],
    ],
    'is_active' => true,
]);

// Create with both role and user approvals
MakerCheckerConfig::create([
    'configurable_type' => Contract::class,
    'action' => 'create',
    'approvals' => [
        'roles' => ['admin' => 1, 'legal' => 1],
        'users' => ['cfo@company.com', 'ceo@company.com'],
    ],
    'description' => 'High-value contracts require CFO and CEO approval',
    'is_active' => true,
]);

// Create with user-only approvals
MakerCheckerConfig::create([
    'configurable_type' => Payment::class,
    'action' => 'create',
    'approvals' => [
        'users' => ['finance@company.com'],
    ],
    'is_active' => true,
]);
```

#### Configuration API

[](#configuration-api)

Create configuration via API:

```
POST /api/maker-checker/configs
Content-Type: application/json

{
    "configurable_type": "App\\Models\\Contract",
    "action": "create",
    "approvals": {
        "roles": {
            "admin": 1,
            "legal": 1
        },
        "users": [
            "cfo@company.com",
            "ceo@company.com"
        ]
    },
    "description": "Contract creation approval workflow"
}
```

Response:

```
{
    "message": "Configuration created successfully",
    "data": {
        "id": 1,
        "configurable_type": "App\\Models\\Contract",
        "configurable_name": "Contract",
        "action": "create",
        "action_label": "Create",
        "approvals": {
            "roles": {"admin": 1, "legal": 1},
            "users": ["cfo@company.com", "ceo@company.com"]
        },
        "role_approvals": {"admin": 1, "legal": 1},
        "user_approvals": ["cfo@company.com", "ceo@company.com"],
        "requires_user_approvals": true,
        "is_active": true
    }
}
```

Update configuration:

```
PUT /api/maker-checker/configs/1
Content-Type: application/json

{
    "approvals": {
        "roles": {"admin": 2},
        "users": ["cfo@company.com"]
    }
}
```

#### UI Mockup

[](#ui-mockup)

A sample UI mockup for the configuration management interface is available at `docs/ui-mockup.html`. Open it in a browser to see how the frontend could interact with these APIs.

API Endpoints
-------------

[](#api-endpoints)

The package provides RESTful API endpoints (prefix configurable):

### Requests

[](#requests)

MethodEndpointDescriptionGET`/api/maker-checker/requests`List all requestsGET`/api/maker-checker/requests/{id}`Get request detailsGET`/api/maker-checker/requests/{id}/approvals`Get approval statusPOST`/api/maker-checker/requests/{id}/approve`Approve a requestPOST`/api/maker-checker/requests/{id}/reject`Reject a requestPOST`/api/maker-checker/requests/{id}/cancel`Cancel own requestGET`/api/maker-checker/requests/statistics`Get request statisticsGET`/api/maker-checker/requests/statuses`List available statuses### Query Parameters

[](#query-parameters)

```
GET /api/maker-checker/requests?status=pending&type=create&team_id=1

```

### Configs (database driver)

[](#configs-database-driver)

MethodEndpointDescriptionGET`/api/maker-checker/configs`List all configsPOST`/api/maker-checker/configs`Create configGET`/api/maker-checker/configs/{id}`Get configPUT`/api/maker-checker/configs/{id}`Update configDELETE`/api/maker-checker/configs/{id}`Delete configPOST`/api/maker-checker/configs/{id}/enable`Enable configPOST`/api/maker-checker/configs/{id}/disable`Disable configGET`/api/maker-checker/configs/export`Export all configsPOST`/api/maker-checker/configs/import`Import configs### Route Configuration

[](#route-configuration)

```
// config/maker-checker.php
'routes' => [
    'enabled' => true,
    'prefix' => 'api',
    'middleware' => ['api', 'auth:sanctum'],
],
```

To customize routes, publish them:

```
php artisan vendor:publish --tag=maker-checker-routes
```

Request Statuses
----------------

[](#request-statuses)

StatusDescription`pending`Awaiting first approval`partially_approved`Has some approvals but not all required`approved`Fully approved and executed`rejected`Rejected by a checker`cancelled`Cancelled by the maker`expired`Expired after timeout`failed`Execution failedHooks
-----

[](#hooks)

Add hooks to the request builder:

```
MakerChecker::request()
    ->toCreate(Post::class, $data)
    ->beforeApproval(function ($request) {
        Log::info('About to approve', ['id' => $request->id]);
    })
    ->afterApproval(function ($request) {
        Notification::send($request->maker, new RequestApproved($request));
    })
    ->beforeRejection(function ($request) {
        // Cleanup logic
    })
    ->afterRejection(function ($request) {
        Notification::send($request->maker, new RequestRejected($request));
    })
    ->onFailure(function ($request) {
        Log::error('Request failed', ['id' => $request->id]);
    })
    ->madeBy(auth()->user())
    ->save();
```

Automatic Model Interception
----------------------------

[](#automatic-model-interception)

The `RequiresApproval` trait provides automatic interception of Eloquent model events. When added to a model, create/update/delete operations return `false` and create a pending approval request instead of executing immediately.

### Basic Usage

[](#basic-usage)

```
use Moffhub\MakerChecker\Traits\RequiresApproval;

class Transaction extends Model
{
    use RequiresApproval;
}
```

### Configuration via Properties

[](#configuration-via-properties)

```
class Transaction extends Model
{
    use RequiresApproval;

    // Only intercept these actions (default: all)
    protected static array $requiresApprovalFor = ['create', 'delete'];

    // Define approval requirements per action
    protected static array $approvalRequirements = [
        'create' => ['finance' => 1],
        'update' => ['finance' => 1],
        'delete' => ['finance' => 1, 'manager' => 1],
    ];
}
```

### Bypassing Approval

[](#bypassing-approval)

```
// Method 1: Static method (resets after one operation)
Transaction::withoutApproval();
Transaction::create([...]); // Bypassed
Transaction::create([...]); // Requires approval again

// Method 2: Instance methods
$transaction = Transaction::createWithoutApproval([...]);
$transaction->updateWithoutApproval(['amount' => 500]);
$transaction->deleteWithoutApproval();

// Method 3: Callback (recommended for multiple operations)
Transaction::withoutApprovalDo(function () {
    Transaction::create([...]);
    Transaction::create([...]);
    // All operations inside are bypassed
});
```

### Handling Interception

[](#handling-interception)

When an operation is intercepted, the model's `save()` or `delete()` returns `false`. Check if it was intercepted and get the pending request:

```
$transaction = new Transaction($data);
$saved = $transaction->save();

if (!$saved && Transaction::wasIntercepted()) {
    $request = Transaction::getInterceptedRequest();

    return response()->json([
        'message' => 'Pending approval',
        'request_id' => $request->id,
        'request_code' => $request->code,
    ], 202); // HTTP 202 Accepted
}

// Clear the intercepted state after handling
Transaction::clearInterceptedRequest();
```

### Exception Mode (Optional)

[](#exception-mode-optional)

If you prefer exception-based handling:

```
// Enable exception mode
Transaction::throwOnIntercept(true);

try {
    $transaction = Transaction::create($data);
} catch (PendingApprovalException $e) {
    $request = $e->getRequest();
    return response()->json(['request' => $request->toArray()], 202);
}
```

### Checking Pending Approvals

[](#checking-pending-approvals)

```
$transaction = Transaction::find(1);

// Check if there are pending approvals
$transaction->hasPendingApproval(); // Any action
$transaction->hasPendingApproval(RequestType::DELETE); // Specific action

// Get pending approval requests
$pending = $transaction->getPendingApprovals();
$pendingDeletes = $transaction->getPendingApprovals(RequestType::DELETE);
```

### Setting the Maker

[](#setting-the-maker)

By default, the trait uses `auth()->user()` as the maker. You can override this:

```
// Set a specific user as the maker
Transaction::setApprovalMaker($adminUser);

// Operations will use $adminUser as the maker
Transaction::create([...]);

// Reset to use auth()->user() again
Transaction::setApprovalMaker(null);
```

Visibility Scoping
------------------

[](#visibility-scoping)

Query requests visible to a user:

```
use Moffhub\MakerChecker\Models\MakerCheckerRequest;

// Get requests visible to current user
$requests = MakerCheckerRequest::visibleTo(auth()->user())->get();

// Users with 'view_any_permission' see all requests
// Others see only their own or their team's requests
```

Expiring Requests
-----------------

[](#expiring-requests)

Enable automatic expiration:

```
// config/maker-checker.php
'request_expiration_in_minutes' => 1440, // 24 hours
```

Run the expiration command (add to scheduler):

```
// app/Console/Kernel.php
$schedule->command('maker-checker:expire-requests')->hourly();
```

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

[](#notifications)

The package can automatically notify approvers when a new request is pending, and notify makers when their request is approved or rejected.

### Enabling Notifications

[](#enabling-notifications)

```
// config/maker-checker.php
'notifications' => [
    'enabled' => true,
    'channels' => ['mail', 'database'], // Notification channels
    'notify_maker' => true,              // Notify maker of approval/rejection
    'sequential' => false,               // See "Sequential Notifications" below
    'user_model' => App\Models\User::class,
    'role_attribute' => 'role',          // Attribute containing user's role
],
```

### Finding Approvers by Role

[](#finding-approvers-by-role)

The package uses an `ApproverResolver` to find users who can approve requests. The default resolver queries users by role attribute:

```
// Default behavior: finds users where role = 'admin'
// When request requires ['admin' => 2], finds all users with role 'admin'
```

For more complex scenarios (Spatie permissions, team-based roles, etc.), implement your own resolver:

```
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Moffhub\MakerChecker\Contracts\ApproverResolver;
use Moffhub\MakerChecker\Models\MakerCheckerRequest;

class CustomApproverResolver implements ApproverResolver
{
    public function getApproversForRole(MakerCheckerRequest $request, string $role): Collection
    {
        // Custom logic: Spatie permissions, team filtering, etc.
        return User::role($role)
            ->where('team_id', $request->team_id)
            ->where('id', '!=', $request->maker_id)
            ->get();
    }

    public function getAllApprovers(MakerCheckerRequest $request): Collection
    {
        // Get all users who can approve any role or are specifically required
        $requiredApprovals = $request->required_approvals ?? [];
        $roles = $requiredApprovals['roles'] ?? $requiredApprovals;
        $users = $requiredApprovals['users'] ?? [];

        $approvers = User::role(array_keys($roles))->get();

        if (!empty($users)) {
            $specificUsers = $this->getApproversByIdentifier($request, $users);
            $approvers = $approvers->merge($specificUsers)->unique('id');
        }

        return $approvers;
    }

    public function getApproversByIdentifier(MakerCheckerRequest $request, array $userIdentifiers): Collection
    {
        return User::whereIn('email', $userIdentifiers)
            ->orWhereIn('id', $userIdentifiers)
            ->where('id', '!=', $request->maker_id)
            ->get();
    }

    public function getApproverByIdentifier(string $identifier): ?Model
    {
        return User::where('email', $identifier)
            ->orWhere('id', $identifier)
            ->first();
    }

    public function userExists(string $identifier): bool
    {
        return $this->getApproverByIdentifier($identifier) !== null;
    }

    public function validateUsersExist(array $userIdentifiers): array
    {
        return array_filter($userIdentifiers, fn($id) => !$this->userExists($id));
    }
}

// Register in AppServiceProvider
$this->app->bind(ApproverResolver::class, CustomApproverResolver::class);
```

### Sequential Notifications

[](#sequential-notifications)

By default, all required roles are notified at once. Enable sequential mode to notify roles one at a time:

```
'notifications' => [
    'sequential' => true,
],
```

In sequential mode:

1. First role is notified when request is created
2. After that role approves, next role is notified
3. Use `MakerChecker::notifyNextApprovers($request)` to manually trigger next notification

```
// After partial approval, notify next approvers
MakerChecker::approve($request, $user, 'editor');

if ($request->isPartiallyApproved()) {
    MakerChecker::notifyNextApprovers($request);
}
```

### Custom Notification Classes

[](#custom-notification-classes)

Override the default notifications with your own:

```
// config/maker-checker.php
'notifications' => [
    'pending_notification' => App\Notifications\CustomPendingNotification::class,
    'approved_notification' => App\Notifications\CustomApprovedNotification::class,
    'rejected_notification' => App\Notifications\CustomRejectedNotification::class,
],
```

Your custom notification should accept a `MakerCheckerRequest` in its constructor:

```
use Illuminate\Notifications\Notification;
use Moffhub\MakerChecker\Models\MakerCheckerRequest;

class CustomPendingNotification extends Notification
{
    public function __construct(
        public MakerCheckerRequest $request,
        public ?string $role = null
    ) {}

    public function via($notifiable): array
    {
        return ['mail', 'database', 'slack']; // Add any channels
    }

    public function toMail($notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject('Custom: Approval Needed')
            ->line("Please review: {$this->request->description}")
            ->action('Review', url("/approvals/{$this->request->code}"));
    }

    // Add toSlack(), toArray(), etc. as needed
}
```

### Manual Notifications

[](#manual-notifications)

Trigger notifications manually when needed:

```
// Notify all approvers about a pending request
MakerChecker::notifyApprovers($request);

// Notify with sequential mode (only first role)
MakerChecker::notifyApprovers($request, sequential: true);

// Notify next approvers after partial approval
MakerChecker::notifyNextApprovers($request);

// Access the notification service directly
$service = MakerChecker::notifications();
$service->notifyPendingApproval($request);
$service->notifyRequestApproved($request);
$service->notifyRequestRejected($request);
```

Lifecycle Callbacks
-------------------

[](#lifecycle-callbacks)

Register callbacks to execute at various points in the request lifecycle.

### Config-Based Callbacks

[](#config-based-callbacks)

Define callbacks in the config file:

```
// config/maker-checker.php
'callbacks' => [
    'on_initiated' => [
        App\MakerChecker\Callbacks\LogNewRequest::class,
        App\MakerChecker\Callbacks\SendSlackNotification::class,
    ],
    'after_approval' => [
        App\MakerChecker\Callbacks\UpdateAuditLog::class,
    ],
    'after_rejection' => [
        App\MakerChecker\Callbacks\NotifyManager::class,
    ],
    'on_failure' => [
        App\MakerChecker\Callbacks\AlertOps::class,
    ],
],
```

Callback classes should implement `RequestCallback` or have a `handle` method:

```
use Moffhub\MakerChecker\Contracts\RequestCallback;
use Moffhub\MakerChecker\Models\MakerCheckerRequest;

class LogNewRequest implements RequestCallback
{
    public function handle(MakerCheckerRequest $request): void
    {
        Log::info('New approval request', [
            'code' => $request->code,
            'type' => $request->type->value,
            'maker' => $request->maker_id,
        ]);
    }
}
```

### Programmatic Callbacks

[](#programmatic-callbacks)

Register callbacks at runtime:

```
// In a service provider or bootstrap file
MakerChecker::callbacks()
    ->onInitiated(function (MakerCheckerRequest $request) {
        // Request was just created
        Log::info('Request initiated', ['code' => $request->code]);
    })
    ->afterApproval(function (MakerCheckerRequest $request) {
        // Request was fully approved and executed
        Notification::send($request->maker, new RequestCompleted($request));
    })
    ->afterRejection(function (MakerCheckerRequest $request) {
        // Request was rejected
        event(new RequestRejectedEvent($request));
    })
    ->onFailure(function (MakerCheckerRequest $request) {
        // Execution failed
        Alert::critical("Request {$request->code} failed");
    });
```

### Available Hooks

[](#available-hooks)

HookWhen Executed`on_initiated`After a new request is created`before_approval`Before approval processing (per-request hooks only)`after_approval`After request is fully approved and executed`before_rejection`Before rejection processing (per-request hooks only)`after_rejection`After request is rejected`on_failure`When request execution failsRate Limiting
-------------

[](#rate-limiting)

All package API routes are rate-limited by default. Configure the limit in your config:

```
// config/maker-checker.php
'routes' => [
    'rate_limit' => env('MAKER_CHECKER_RATE_LIMIT', 60), // requests per minute
],
```

Rate limiting is keyed by the authenticated user's ID or by IP address for unauthenticated requests. Set to `0` or `null` to disable rate limiting.

The rate limiter is registered under the name `maker-checker`, so you can reference it in your own routes if needed:

```
Route::middleware('throttle:maker-checker')->group(function () {
    // Your custom maker-checker routes
});
```

Audit Logging
-------------

[](#audit-logging)

The package automatically logs all approval actions (approve, reject, cancel, fail) with full context.

### Configuration

[](#configuration-1)

```
// config/maker-checker.php
'audit' => [
    'enabled' => env('MAKER_CHECKER_AUDIT_ENABLED', true),
    'driver' => env('MAKER_CHECKER_AUDIT_DRIVER', 'database'),
    'table_name' => 'maker_checker_audit_logs',
    'log_channel' => null, // Laravel log channel for 'log' driver
],
```

### Drivers

[](#drivers)

- **`database`** (default): Writes audit entries to the `maker_checker_audit_logs` table. Best for querying and reporting.
- **`log`**: Writes audit entries to a Laravel log channel. Best for high-throughput systems where you want to offload to external log aggregation (ELK, Datadog, etc.).

### Logged Data

[](#logged-data)

Each audit entry includes:

- `request_id` - The maker-checker request ID
- `actor_type` / `actor_id` - Who performed the action (morph relationship)
- `action` - The action performed (approved, rejected, cancelled, partially\_approved, failed)
- `previous_status` - The request status before the action
- `new_status` - The request status after the action
- `ip_address` - The IP address of the actor
- `metadata` - Additional context (e.g., exception messages for failures)

Conditional Configuration
-------------------------

[](#conditional-configuration)

When using the database config driver, you can define conditions that determine which configuration applies based on the request payload. This allows different approval requirements for different scenarios.

### Supported Operators

[](#supported-operators)

OperatorDescriptionExample Value`=`Equal to`50000``!=`Not equal to`"draft"``>`Greater than`10000``>=`Greater than or equal`5000``team_id;
    }

    // ... other contract methods
}
```

2. **Pass team ID when creating requests:**

```
MakerChecker::request()
    ->toCreate(Invoice::class, $data, teamId: auth()->user()->team_id)
    ->madeBy(auth()->user())
    ->save();
```

Or use the builder methods:

```
MakerChecker::request()
    ->toCreate(Invoice::class, $data, requiredApprovals: [], teamId: $teamId)
    ->madeBy(auth()->user())
    ->save();
```

3. **Enable team scoping for notifications:**

```
// config/maker-checker.php
'notifications' => [
    'team_scoping' => true,
    'team_attribute' => 'team_id', // attribute on user model
],
```

4. **Create team-scoped configs** (database driver):

```
MakerCheckerConfig::create([
    'configurable_type' => Invoice::class,
    'action' => 'create',
    'approvals' => ['manager' => 1],
    'team_id' => 42, // Applies only to team 42
    'is_active' => true,
]);
```

### Visibility

[](#visibility)

Requests are automatically filtered by team when using `scopeVisibleTo`:

```
// Users only see requests from their team
$requests = MakerCheckerRequest::visibleTo(auth()->user())->get();
```

Users with the `view_any_permission` bypass team filtering and see all requests.

Custom ApproverResolver
-----------------------

[](#custom-approverresolver)

The default `ApproverResolver` finds approvers by querying a `role` attribute on the user model. For more complex scenarios, implement your own resolver.

### Example: Spatie Permissions Integration

[](#example-spatie-permissions-integration)

```
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Moffhub\MakerChecker\Contracts\ApproverResolver;
use Moffhub\MakerChecker\Models\MakerCheckerRequest;

class SpatieApproverResolver implements ApproverResolver
{
    public function getApproversForRole(MakerCheckerRequest $request, string $role): Collection
    {
        return User::permission("maker-checker.approve.{$role}")
            ->when($request->team_id, fn($q) => $q->where('team_id', $request->team_id))
            ->where('id', '!=', $request->maker_id)
            ->get();
    }

    public function getAllApprovers(MakerCheckerRequest $request): Collection
    {
        $requiredApprovals = $request->required_approvals ?? [];
        $roles = $requiredApprovals['roles'] ?? $requiredApprovals;
        $users = $requiredApprovals['users'] ?? [];

        $approvers = collect();

        foreach (array_keys($roles) as $role) {
            $approvers = $approvers->merge($this->getApproversForRole($request, $role));
        }

        if (!empty($users)) {
            $approvers = $approvers->merge($this->getApproversByIdentifier($request, $users));
        }

        return $approvers->unique('id');
    }

    public function getApproversByIdentifier(MakerCheckerRequest $request, array $userIdentifiers): Collection
    {
        return User::where(function ($query) use ($userIdentifiers) {
            $query->whereIn('email', $userIdentifiers)
                ->orWhereIn('id', $userIdentifiers);
        })->get();
    }

    public function getApproverByIdentifier(string $identifier): ?Model
    {
        return User::where('email', $identifier)
            ->orWhere('id', $identifier)
            ->first();
    }

    public function userExists(string $identifier): bool
    {
        return $this->getApproverByIdentifier($identifier) !== null;
    }

    public function validateUsersExist(array $userIdentifiers): array
    {
        return array_filter($userIdentifiers, fn($id) => !$this->userExists($id));
    }
}
```

Register it in your `AppServiceProvider`:

```
$this->app->bind(ApproverResolver::class, SpatieApproverResolver::class);
```

Custom ExecutableRequest
------------------------

[](#custom-executablerequest)

Create custom executable actions for complex operations that need approval:

```
use Moffhub\MakerChecker\Contracts\ExecutableRequest;
use Moffhub\MakerChecker\Models\MakerCheckerRequest;

class BulkUserImport extends ExecutableRequest
{
    /**
     * Execute the approved action.
     */
    public function execute(MakerCheckerRequest $request): void
    {
        $payload = $request->payload;

        foreach ($payload['users'] as $userData) {
            User::create([
                'name' => $userData['name'],
                'email' => $userData['email'],
                'role' => $userData['role'] ?? 'user',
                'team_id' => $request->team_id,
            ]);
        }
    }

    /**
     * Fields used to determine request uniqueness.
     * Prevents duplicate import requests with the same file hash.
     */
    public function uniqueBy(): array
    {
        return ['file_hash'];
    }

    /**
     * Runs before the request is approved.
     * Use for pre-flight validation.
     */
    public function beforeApproval(MakerCheckerRequest $request): void
    {
        $payload = $request->payload;

        // Verify no duplicate emails in the import
        $emails = array_column($payload['users'], 'email');
        $existing = User::whereIn('email', $emails)->pluck('email');

        if ($existing->isNotEmpty()) {
            throw new \RuntimeException(
                'Import contains existing emails: ' . $existing->implode(', ')
            );
        }
    }

    /**
     * Runs after successful approval and execution.
     */
    public function afterApproval(MakerCheckerRequest $request): void
    {
        $count = count($request->payload['users'] ?? []);
        Log::info("Bulk import completed: {$count} users imported", [
            'request_id' => $request->id,
            'team_id' => $request->team_id,
        ]);
    }

    /**
     * Runs before the request is rejected.
     */
    public function beforeRejection(MakerCheckerRequest $request): void
    {
        // Optional: cleanup temporary files
    }

    /**
     * Runs after the request is rejected.
     */
    public function afterRejection(MakerCheckerRequest $request): void
    {
        Notification::send($request->maker, new ImportRejectedNotification($request));
    }

    /**
     * Runs when execution fails.
     */
    public function onFailure(MakerCheckerRequest $request): void
    {
        Log::error('Bulk import failed', [
            'request_id' => $request->id,
            'exception' => $request->exception,
        ]);
    }
}
```

Use it:

```
MakerChecker::request()
    ->toExecute(BulkUserImport::class, [
        'file_hash' => md5_file($uploadedFile->path()),
        'users' => $parsedUsers,
    ])
    ->withApprovals(['hr_manager' => 1, 'admin' => 1])
    ->madeBy(auth()->user())
    ->save();
```

Testing
-------

[](#testing)

```
composer test
```

Run the full check suite:

```
composer check-code  # Runs lint, phpstan, and tests
```

Configuration Reference
-----------------------

[](#configuration-reference)

OptionDefaultDescription`ensure_requests_are_unique``true`Prevent duplicate pending requests`request_expiration_in_minutes``null`Auto-expire after N minutes`default_approval_count``1`Default approvals when not specified`table_name``maker_checker_requests`Requests table name`config_table_name``maker_checker_configs`Configs table name`delete_on_completion``true`Delete requests after execution`soft_delete_on_completion``false`Soft delete instead`view_any_permission``maker-checker.view-any`Permission to view all requests`config_driver``file``file` or `database``cache_config``true`Cache database configs`config_cache_ttl``3600`Cache TTL in seconds`routes.rate_limit``60`Rate limit per minute (0 to disable)`notifications.enabled``false`Enable automatic notifications`notifications.channels``['mail', 'database']`Notification delivery channels`notifications.notify_maker``true`Notify maker on approval/rejection`notifications.sequential``false`Notify roles one at a time`notifications.role_attribute``role`User model attribute for role`audit.enabled``true`Enable audit logging`audit.driver``database`Audit storage: `database` or `log``audit.table_name``maker_checker_audit_logs`Audit log table name`audit.log_channel``null`Laravel log channel for `log` driverTroubleshooting
---------------

[](#troubleshooting)

### "No authenticated user found" error

[](#no-authenticated-user-found-error)

This error occurs when using the convenience methods (`MakerChecker::create()`, `MakerChecker::approve()`) without an authenticated user. Solutions:

- Ensure the user is authenticated before calling these methods
- Use the request builder with `->madeBy($user)` to explicitly pass a user
- For console commands or jobs, use the builder pattern instead of convenience methods

### "Request checker cannot be the same as the maker"

[](#request-checker-cannot-be-the-same-as-the-maker)

By default, the same user cannot both create and approve a request. To allow this for specific users (e.g., admins in development):

```
// .env
MAKER_CHECKER_WHITELISTED_EMAILS=admin@example.com,super@example.com
```

### "The request model passed must be an instance of..."

[](#the-request-model-passed-must-be-an-instance-of)

This happens when:

- The `request_model` config points to a class that doesn't extend `MakerCheckerRequest`
- The config hasn't been published or is outdated

Fix: Ensure your custom model extends `MakerCheckerRequest`:

```
class CustomRequest extends \Moffhub\MakerChecker\Models\MakerCheckerRequest
{
    // Your customizations
}
```

### Duplicate request exceptions

[](#duplicate-request-exceptions)

When `ensure_requests_are_unique` is `true`, creating a request with the same payload as an existing pending request throws a `DuplicateRequestException`. Solutions:

- Use `uniqueBy()` on the builder to specify which fields determine uniqueness
- Set `ensure_requests_are_unique` to `false` if duplicates are acceptable
- Approve or cancel existing pending requests first

### Notifications not sending

[](#notifications-not-sending)

1. Ensure notifications are enabled: `'notifications.enabled' => true`
2. Verify `user_model` is set or `auth.providers.users.model` is configured
3. Check that your user model uses Laravel's `Notifiable` trait
4. Verify the `ApproverResolver` returns users for the required roles
5. Check your notification channels configuration

### Config validation errors on boot

[](#config-validation-errors-on-boot)

The package validates configuration when the application boots (except during tests). Common issues:

- `default_approval_count` must be &gt;= 1
- `config_driver` must be `file` or `database`
- `request_model` must be a class extending `MakerCheckerRequest`
- `whitelisted_models.maker` and `whitelisted_models.checker` must be arrays

### Database config not applying

[](#database-config-not-applying)

When using the `database` config driver:

1. Ensure the config driver is set: `'config_driver' => 'database'`
2. Run migrations: `php artisan migrate`
3. Check configs are `is_active: true`
4. Clear config cache if changes aren't reflected: `php artisan cache:clear`
5. Verify the `team_id` matches (team-specific configs only apply to that team)

### Rate limiting too aggressive

[](#rate-limiting-too-aggressive)

Adjust the rate limit per minute:

```
// config/maker-checker.php
'routes' => [
    'rate_limit' => 120, // Increase to 120 per minute
],
```

Or disable rate limiting entirely:

```
'routes' => [
    'rate_limit' => 0, // Disabled
],
```

Known Limitations
-----------------

[](#known-limitations)

1. **No built-in queue support for fulfillment**: When a request is approved, the underlying operation (create/update/delete/execute) runs synchronously within the approval request. For long-running operations, implement your own queue dispatch inside an `ExecutableRequest`.
2. **JSON payload comparison**: Duplicate request checking uses JSON field comparisons (`payload->field`), which may behave differently across database engines (MySQL vs PostgreSQL vs SQLite).
3. **Single approval per user**: A user can only approve a request once. They cannot approve under multiple roles for the same request.
4. **No partial rollback**: If fulfillment fails after approval, the request is marked as `failed` but any partial side effects from hooks (`beforeApproval`) are not rolled back.
5. **Config driver is global**: You cannot use different config drivers for different models. The `config_driver` setting applies to all models.
6. **Morph map dependency**: The package uses polymorphic relationships for maker/checker/subject. If you change your morph map after requests are created, existing requests may break.
7. **No built-in approval deadlines**: While requests can expire, there is no built-in deadline per approval step in a multi-role chain. All roles have the same expiration window.

Performance
-----------

[](#performance)

For high-traffic production deployments, see [docs/PERFORMANCE.md](docs/PERFORMANCE.md) for:

- Recommended database indexes
- Query optimization tips
- Config caching recommendations
- Approval chain resolution at scale

License
-------

[](#license)

MIT License. See [LICENSE](LICENSE) for details.

###  Health Score

46

—

FairBetter than 93% of packages

Maintenance85

Actively maintained with recent releases

Popularity23

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity55

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 ~40 days

Recently: every ~81 days

Total

14

Last Release

109d ago

PHP version history (3 changes)v0.0.1PHP ^8.2

v0.0.13PHP ^8.3

v0.1.0PHP ^8.3 || ^8.4 || ^8.5

### Community

Maintainers

![](https://www.gravatar.com/avatar/6b5c3549bf8f8bab163b760fe2023fe357a3c85ed366e052a735ff5c50f84518?d=identicon)[Moffrough](/maintainers/Moffrough)

---

Top Contributors

[![MOFFROUGH](https://avatars.githubusercontent.com/u/20206597?v=4)](https://github.com/MOFFROUGH "MOFFROUGH (42 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/moffhub-maker-checker/health.svg)

```
[![Health](https://phpackages.com/badges/moffhub-maker-checker/health.svg)](https://phpackages.com/packages/moffhub-maker-checker)
```

###  Alternatives

[bezhansalleh/filament-shield

Filament support for `spatie/laravel-permission`.

2.8k2.9M88](/packages/bezhansalleh-filament-shield)[illuminate/auth

The Illuminate Auth package.

9327.3M1.0k](/packages/illuminate-auth)[olssonm/l5-very-basic-auth

Laravel stateless HTTP basic auth without the need for a database

1662.5M1](/packages/olssonm-l5-very-basic-auth)[stechstudio/laravel-jwt

Helper package that makes it easy to generate, consume, and protect routes with JWT tokens in Laravel

126117.6k](/packages/stechstudio-laravel-jwt)[scaler-tech/laravel-saml2

SAML2 Service Provider integration for Laravel applications, based on OneLogin toolkit

2737.5k](/packages/scaler-tech-laravel-saml2)[truckersmp/steam-socialite

Laravel Socialite provider for Steam OpenID.

1516.7k](/packages/truckersmp-steam-socialite)

PHPackages © 2026

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