spatie/laravel-deleted-models
Automatically copy deleted Eloquent model attributes into a deleted_models table to create a recycle bin for records. Restore deleted models by ID when needed. An alternative to soft deletes for preserving deletion history without keeping rows in place.
Installation:
composer require spatie/laravel-deleted-models
Publish the migration (creates deleted_models table):
php artisan vendor:publish --provider="Spatie\DeletedModels\DeletedModelsServiceProvider"
php artisan migrate
Enable for a Model:
Add use \Spatie\DeletedModels\HasDeletedModels; to your model and implement the trait:
use Spatie\DeletedModels\HasDeletedModels;
class BlogPost extends Model
{
use HasDeletedModels;
}
First Use Case: Delete a model to trigger automatic archival:
$post = BlogPost::find(1);
$post->delete(); // Copies to `deleted_models` with `deleted_at` timestamp
Soft Deletes + Archival:
Combine with Laravel’s SoftDeletes for dual-layer recovery:
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\DeletedModels\HasDeletedModels;
class BlogPost extends Model
{
use SoftDeletes, HasDeletedModels;
protected $dates = ['deleted_at', 'archived_at'];
}
delete() → Soft deletes + archives to deleted_models.forceDelete() → Permanently deletes (bypasses archival).Restoration:
// Restore from archive (creates new record in original table)
$restored = BlogPost::restore(1);
// Restore soft-deleted (if using SoftDeletes)
$restored = BlogPost::withTrashed()->find(1)->restore();
Querying Archived Models:
Use the DeletedModel facade or query the deleted_models table directly:
use Spatie\DeletedModels\Facades\DeletedModel;
$archived = DeletedModel::for(BlogPost::class)->get();
public function scopeArchived($query)
{
return $query->whereHas('deletedModels');
}
deleted events to log or notify:
BlogPost::deleted(function ($model) {
// Custom logic (e.g., audit logs)
});
Route::post('/posts/{id}/restore', function (BlogPost $post) {
return $post->restore();
});
Foreign Key Constraints:
Duplicate IDs on Restoration:
$restored = BlogPost::restore(1)->setAttribute('slug', uniqid());
Mass Deletion:
Model::destroy([1, 2, 3]) archives each model individually. For bulk archival, use:
BlogPost::whereIn('id', [1, 2, 3])->get()->each->delete();
Custom Columns:
protected $hidden = ['api_token'];
protected $casts = ['password' => 'encrypted'];
deleted_models table exists and the trait is applied.Schema::table('deleted_models', function (Blueprint $table) {
$table->index(['model_type', 'model_id']);
});
Custom Archive Table:
Override the default table name in config/deleted-models.php:
'table_name' => 'custom_deleted_models',
Archive Logic:
Extend the archive method in the trait:
protected function archive()
{
$this->setAttribute('archived_reason', 'manual cleanup');
parent::archive();
}
Soft Delete Override: Disable soft deletes for archival-only behavior:
use HasDeletedModels;
class BlogPost extends Model
{
use HasDeletedModels;
// No SoftDeletes trait
}
How can I help you explore Laravel packages today?