Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Approvals Laravel Package

eightynine/filament-approvals

Add approval workflows to Filament resources: route records to one or more approvers, track pending, approved, and rejected states with a full history, and gate actions on approval status, all configured in code and managed from the admin panel.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish the plugin scaffolding first — this package ships config and migrations that you must run before touching a model:

composer require eightynine/filament-approvals
php artisan vendor:publish --tag="filament-approvals-config"
php artisan vendor:publish --tag="filament-approvals-migrations"
php artisan migrate

Then register the plugin on the panel you want approvals to appear in:

// app/Providers/Filament/AdminPanelProvider.php
public function panel(Panel $panel): Panel
{
    return $panel
        ->plugin(\Eightynine\FilamentApprovals\FilamentApprovalsPlugin::make());
}

Where to look first: after publishing, read the generated config/filament-approvals.php and the migration before writing any code. That config file is the single best map of what the package actually does in your installed version — approver resolution, notification behaviour, and table names are usually all declared there. Compare it against the README, because small packages like this (120 stars, 0 dependents, last release Oct 2025) often have a README that lags the code. The vendor/eightynine/filament-approvals/src directory is small enough to skim in ten minutes and will answer any naming question definitively.

First use case: take one existing model — a PurchaseOrder, LeaveRequest, Post, anything with a status column — add the package's approvable trait/contract to it, and drop the approve/reject table actions onto its Filament resource. That vertical slice (one model, one resource, one approver) is the fastest way to see whether the package's opinionated workflow matches how your application already thinks about state.

Implementation Patterns

Model-layer integration. The package follows the standard Filament plugin shape: a contract plus a trait on the approvable model, with approvals stored in a separate table keyed by morph relation. Wire it up once on your base model or a shared trait so every approvable model gets identical behaviour:

class PurchaseOrder extends Model implements Approvable
{
    use HasApprovals;
}

Keeping the status enum, the trait, and any canBeApproved() guard on the model — not in the Filament resource — is what lets you reuse the same rules from jobs, API controllers, and console commands.

Resource integration. The highest-leverage pattern is table actions, because approvals are almost always triaged in bulk from a list view:

->actions([
    ApproveAction::make(),
    RejectAction::make()->requiresConfirmation(),
])
->bulkActions([
    ApproveBulkAction::make(),
])

Gate visibility on the record's own state (->visible(fn ($record) => $record->isPending())), not just on permission — otherwise approvers see buttons on already-decided records and learn to distrust the UI.

Approver resolution. Most implementations of this pattern resolve approvers either statically (a role/permission check) or dynamically (a method on the model returning user IDs). If the package supports the dynamic form, prefer it: hardcoding role:manager in config works until the day a record needs a different approver, and by then you have a data migration on your hands.

Notifications and side effects. Treat approval transitions as domain events, not UI events. If the package fires events on approve/reject, register listeners in a service provider rather than stuffing ->after() callbacks into every Filament action — you'll want the same side effects when approvals are triggered from a scheduled command or an imported batch.

Multi-step chains. Where the package supports sequential approval, model the chain explicitly (a step index or an ordered approver list) rather than as a chain of separate resources. The moment you need "if amount > 10k, add a finance step," implicit ordering breaks down.

Testing. Approval logic is exactly the kind of thing that deserves feature tests, because it's easy to break silently. Assert on the model's state after calling the action, not on the UI:

$this->actingAs($manager)
    ->livewire(ApproveAction::class, ['record' => $order->id])
    ->callAction('approve');

expect($order->refresh()->isApproved())->toBeTrue();

Gotchas and Tips

Verify compatibility before committing. With zero dependents and a modest star count, this is a package you should smoke-test against your exact Filament major version on day one. Filament v3 → v4 changed action namespaces and modal APIs; a plugin released in October 2025 may target one and not the other. Install it in a scratch branch, build the one-model slice above, and only then refactor real resources.

Migrations before plugin registration. Registering the plugin before the approvals table exists produces confusing "column not found" errors from Filament's table queries rather than a clear failure. Publish, migrate, then enable.

Trait boot-method collisions. If you already have a bootHasApprovals-style method or your own approvals() morphMany, the trait will collide or silently win. Grep your models for the method names the trait defines before adding it broadly.

Stale approvals after edits. The most common production bug in approval workflows: a record is approved, someone edits it, and it stays approved. Hook your model's updating event to reset to pending (or invalidate) when material fields change — the package won't do this for you, because it can't know which fields matter.

Bulk actions bypass per-record visibility. Filament's bulk actions only check the bulk action's own visible(), not the row action's. Add an explicit per-record guard inside the bulk handler or a ->deselectRecordsAfterCompletion(), and test with a mixed selection of pending and approved records.

Authorization defaults are often permissive. Confirm whether the package ships policies or relies on Gate. If it's the latter, add explicit policy methods — an approval plugin that lets any authenticated admin approve anything is a compliance finding waiting to happen.

N+1 on approval relations. Eager-load the approval relation on list pages (->modifyQueryUsing(fn ($q) => $q->with('latestApproval'))) once you display approver names or timestamps in the table.

Debugging checklist. When an action doesn't appear: (1) is the record's status actually what you think — dump it; (2) is the model correctly implementing the contract, or did PHP silently accept a partial implementation; (3) is the plugin registered on this panel, not just the default one; (4) check the Laravel log for swallowed exceptions from the action's ->action() closure.

Extension points. The config file and any publishable views are your safe customization surface. Subclassing the package's action classes is usually cleaner than ->action(fn () => ...) overrides, because you keep the built-in modal, notification, and authorization wiring intact. Avoid editing vendor/ — pin the version in composer.json and copy behavior via your own subclass if you need to change it.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules
loco/loco
starfederation/datastar-php
yupmin/modern-phystrix
stephenjude/extended-artisan-commands
lumphp/laravel-macroable
lumphp/laravel-collections
lumphp/laravel-contracts
sermepa/sermepa
arkecosystem/fortify