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 Recycle Bin Laravel Package

muazzambuilds/filament-recycle-bin

Add a recycle bin to your Filament resources. Deleted records move to a dedicated bin where you can browse, restore, or permanently delete them, with bulk actions and a familiar, native Filament interface for safer data management.

View on GitHub
Deep Wiki
Context7

Getting Started

What it is: a single Filament v5 panel page that aggregates every soft-deletable model you register, so admins restore or hard-delete trashed rows from one place instead of hunting through each Resource's TrashedFilter.

Prerequisites: Filament v5 (this is not a v3/v4 package), Laravel with SoftDeletes on the models you want to expose, and a working panel provider.

Minimal setup:

composer require muazzambuilds/filament-recycle-bin
php artisan vendor:publish --tag="filament-recycle-bin-config"   # verify tag in README

Register the plugin on the panel you want the bin to live in:

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

Then declare which models go in the bin — typically in config/recycle-bin.php or via a fluent plugin method:

->plugin(
    RecycleBinPlugin::make()
        ->models([
            \App\Models\Invoice::class,
            \App\Models\Customer::class,
        ])
)

Where to look first, in order:

  1. config/recycle-bin.php — the model registry, navigation label/group, and any per-model column or label overrides live here.
  2. The plugin class (RecycleBinPlugin) — confirms whether options are config-driven or fluent.
  3. The page class — this is what you subclass if you need custom table columns or scoping.

First use case: register one model, delete a record from its Resource, open the Recycle Bin page in the panel, hit Restore. That single loop validates the whole install; if the row doesn't appear, the problem is almost always a missing SoftDeletes trait or a model not in the registry.

Implementation Patterns

Start narrow, then widen. Register one or two high-value models (invoices, orders, CMS pages) rather than every soft-deletable table on day one. A central bin with fifteen model types is a UI problem before it's a technical one — add grouping/labels per model as you go.

Use it as the recovery surface, keep Resource-level filters for context. Filament's own TrashedFilter is still the right tool when an editor is inside a Resource and wants to see deleted siblings. The recycle bin is the cross-model, admin-level surface. Don't try to remove one in favour of the other.

Authorization is the whole game here. Restore is usually safe; forceDelete is not. Gate them separately — a restore ability for support staff, forceDelete for a small admin role — via policies on each model or a single gate checked in the page. If the package doesn't ship granular permissions, wrap the page's mount/actions yourself.

Multi-tenant panels. The single most important integration check: confirm the bin's query respects your tenancy scope. If you use Filament's tenancy or a global scope on tenant_id, verify a tenant admin cannot see another tenant's trashed rows. If the package queries withTrashed() without the scope, override the page's table query:

protected function getTableQuery(): Builder
{
    return parent::getTableQuery()
        ->where('tenant_id', filament()->getTenant()?->id);
}

Bulk operations. Restore/force-delete in bulk is where this page earns its keep during incident cleanup. Pair bulk force-delete with a confirmation modal and, ideally, an audit log entry (model observer or a ForceDeleting listener) since the operation leaves no trace.

Audit trail pattern. Hook model events rather than the UI — a deleted/restored/forceDeleted observer on registered models gives you a durable record regardless of whether the action came from the bin, a Resource, or a tinker session.

Programmatic reuse. The restore/delete logic is useful outside the UI (support scripts, queued cleanup of old trashed records). Check whether the package exposes a service or action class; if not, Model::onlyTrashed()->find($id)->restore() is the fallback and is fine.

Scheduled purge. Combine the bin with a retention policy: a nightly command that force-deletes trashed records older than N days per model. The bin then becomes the "last N days" safety net rather than an ever-growing table.

Testing. Feature-test the page with livewire(RecycleBinPage::class)->call('restore', $record->id) style assertions, and separately test that a non-privileged user cannot reach the page or trigger force-delete. Cover the tenancy case explicitly if you're multi-tenant.

Gotchas and Tips

Missing SoftDeletes = silent no-show. The bin only sees models using the trait. If a model "isn't in the bin," check the trait, then the registry, then whether the config is cached (php artisan config:clear).

It's Filament v5 only. Composer will happily leave you on v4 with an incompatible constraint if you're mid-upgrade. Check composer why-not if the page renders blank or the plugin class is missing.

Force delete bypasses your normal safety rails. No deleting observer will save you the way it might on a soft delete — the row is gone. Restrict the ability, add a typed confirmation, and consider a "copy row to archive table" listener before forceDelete for anything regulated.

Early-stage package — pin and read the source. At ~11 stars and zero dependents, this is not battle-tested. Pin an exact version in composer.json, read the page class before relying on tenancy or authorization behaviour, and expect breaking changes between minor releases. There's also no third-party security review; treat it as you would your own code.

Timezone drift on deleted_at. The "deleted 2 hours ago" column and any retention purge both hinge on deleted_at. If your app timezone differs from the DB's, off-by-hours sorting looks like a bug but isn't.

Large trashed tables. withTrashed() across many models with no index on deleted_at gets slow fast. Ensure deleted_at is indexed on every registered table, and watch for N+1 on relation-heavy tables in the bin view — the cross-model table is exactly where eager loading gets forgotten.

Relation cascades. Restoring a parent does not restore children you soft-deleted alongside it, and force-deleting a parent may cascade at the DB level. Decide explicitly per model and document it, or you'll get half-restored object graphs.

Navigation clutter. The page registers into your panel navigation by default. Set the group, sort, and label in config, or hide it and link from a dashboard widget if the panel is already dense.

Extension points worth knowing: the page class (table columns, query scoping), the plugin's fluent config, and per-model labels/columns. If the package doesn't expose what you need, subclassing the page is the cleanest escape hatch — much better than forking.

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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast