amjadiqbal / filament-deleted-records
A recycle bin for Filament 4 and 5: browse, preview, restore or permanently delete records kept by spatie/laravel-deleted-models.
Package info
github.com/amjadiqbal/filament-deleted-records
pkg:composer/amjadiqbal/filament-deleted-records
Requires
- php: ^8.3
- filament/filament: ^4.0|^5.0
- illuminate/contracts: ^12.0|^13.0
- spatie/laravel-deleted-models: ^1.1.2
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
- pestphp/pest-plugin-livewire: ^3.0|^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Deleted Records for Filament
A recycle bin for your Filament panel. It lists every record that spatie/laravel-deleted-models kept when it was deleted, shows the stored attributes, and lets you restore a record or delete it permanently, one at a time or in bulk. It works with Filament 4 and 5.
Docs: amjad.com.pk/filament/plugins/filament-deleted-records
What it does
spatie/laravel-deleted-models copies a model's attributes to a deleted_models table when you delete it, and gives
you Model::restore($key) in code. This plugin adds the screen for it:
- One list of everything deleted, newest first. Each row shows a readable title (the first of
name,title,label,subject,email,number,referenceorslugthat is filled), the key, the model type, who deleted it and when. - Preview before you restore. A modal shows every stored attribute and whether a restore can work. It warns you if a
row with that key is still in the table (soft-deleted rows count, because the key is taken), or if the model class
no longer exists. The warning does not disable Restore: a restore that fails is reported and the copy is kept.
Passwords, remember tokens, API tokens and two-factor secrets are masked on screen. They are still stored in
deleted_models, because the base package keeps hidden attributes so a restore is complete. - Restore puts the record back with the same key and attributes. It goes through the model's own
restore(), so yourbeforeRestoringModelandafterRestoringModelhooks and the package's events run. When the panel has a resource for the model, the notification links to the restored record. - Delete permanently removes the stored copy for good.
- Bulk restore and bulk permanent delete. The notification counts what was done, what failed (for example, the key is taken; the copy is kept) and what was skipped because the user may not touch it.
- Filters by model type, by who deleted it and by date range. Search matches the key exactly, and the title
attributes (see
titleAttributes()) case-insensitively. Other stored attributes are not searched, so masked values cannot be probed through search. - Who deleted it. An optional migration adds a
deleted_by_idcolumn, and the plugin fills it with the user signed in to the panel's guard when a copy is written. Deletes from queued jobs, console commands and scheduled tasks have no signed-in user, so the column stays empty for them. A user who was later removed shows as#id. - Permissions. By default, anyone who can open the panel can list, preview, restore and permanently delete. To
narrow that, give the original model's policy
restore/forceDeletemethods, register a policy for the deleted-record model, or pass one callback. See Permissions. - Works in light and dark mode with no custom theme or build step. Translatable (English included).
- Multi-tenant panels: the page is not scoped to a tenant, so it lists every tenant's deleted records together. Use
authorizeUsing()to keep it to central admins, or extend the resource and scopegetEloquentQuery()yourself.
Requirements
- PHP 8.3+ (PHP 8.4+ on Laravel 13, which
spatie/laravel-deleted-modelsrequires there) - Laravel 12 or 13
- Filament 4 or 5
Installation
composer require amjadiqbal/filament-deleted-records
This also installs spatie/laravel-deleted-models. Publish and run both migrations: the deleted_models table, then
the optional deleted_by_id column. The column is what powers the "Deleted by" column and filter.
php artisan vendor:publish --tag="deleted-models-migrations" php artisan vendor:publish --tag="filament-deleted-records-migrations" php artisan migrate
Add the KeepsDeletedModels trait to every model whose deleted records you want to keep:
use Illuminate\Database\Eloquent\Model; use Spatie\DeletedModels\Models\Concerns\KeepsDeletedModels; class Invoice extends Model { use KeepsDeletedModels; }
Register the plugin on your panel:
use AmjadIqbal\FilamentDeletedRecords\DeletedRecordsPlugin; public function panel(Panel $panel): Panel { return $panel // ... ->plugin(DeletedRecordsPlugin::make()); }
A "Deleted records" page now appears in the navigation, with a badge showing how many records are in the bin.
Stored copies are kept for 365 days by default. To prune older ones, schedule Laravel's model:prune for the
DeletedModel class, as described in the
spatie/laravel-deleted-models README.
Configuration
Every option is optional:
DeletedRecordsPlugin::make() ->navigationGroup('System') ->navigationLabel('Recycle bin') ->navigationSort(90) ->navigationBadge(false) // hide the count badge ->models([Invoice::class, Customer::class]) // only these models; empty shows all ->modelLabels([Invoice::class => 'Sales invoice']) ->titleAttributes(['number', 'name']) // attributes tried, in order, for the row title ->maskedAttributes(['iban', 'secret']) // merged with the defaults; pass false as the 2nd argument to replace them ->userTitleAttribute('email') // shown in "Deleted by" (default: name) ->resource(MyDeletedRecordResource::class); // extend the resource to change anything else
To change the tracking of who deleted a record, publish the config file:
php artisan vendor:publish --tag="filament-deleted-records-config"
return [ 'track_deleted_by' => true, // set to false to stop recording deleted_by_id 'user_model' => null, // null uses the default auth guard's user model; set it if your panel uses another guard ];
Permissions
The plugin checks each action in this order:
-
authorizeUsing(), if you pass a callback. It decides everything:DeletedRecordsPlugin::make() ->authorizeUsing(fn (string $ability, ?DeletedModel $record, ?User $user): bool => $user?->isAdmin() ?? false);
$abilityis one ofviewAny,view,restore,forceDelete,restoreAnyorforceDeleteAny. -
A policy for the deleted-record model (
Spatie\DeletedModels\Models\DeletedModel, or your own class set indeleted-models.model), if you register one. It gets the same abilities. -
The original model's policy. For a record, if that policy has a method named exactly
view,restoreorforceDelete, it decides, and it receives the record as it would be restored. SoInvoicePolicy::restore()decides who can restore a deleted invoice. A policy without that method does not block the action. -
Otherwise the action is allowed. Access to the panel is then the only gate. This includes
viewAny,restoreAnyandforceDeleteAny(listing the page and showing the bulk actions) whenever neither 1 nor 2 applies.
Bulk actions also check each record, skip the ones the user may not touch, and say how many were skipped.
Testing
composer test
scripts/ci-local.sh runs Pint, PHPStan and the tests on PHP 8.3 to 8.5, Laravel 12 and 13, and Filament 4 and 5.
The tests use SQLite; set DB_TEST_DRIVER=mysql or pgsql (with DB_PORT, DB_USERNAME, DB_PASSWORD,
DB_DATABASE) to run them on a real server.
Changelog
See CHANGELOG.md.
Support
- 🐛 Bug or feature request: open an issue
- 🔒 Security issue: please report it privately through GitHub's security tab
Need this customised, or something built?
I'm available for custom development, package integration, and technical consulting.
More packages
This is part of a family of open-source packages. See all of them.
| filament-change-requests | Hold record edits in Filament for approval, with a field-by-field diff |
| filament-url-image-uploader | Upload images to Filament from a URL |
| refresher | Restore a cached test database instead of migrate:fresh |
Credits
- Amjad Iqbal
- Storage and restore: spatie/laravel-deleted-models by Spatie (MIT). This plugin is not affiliated with or endorsed by Spatie.
License
The MIT License (MIT). See LICENSE.md.

