Reader Stacks

Soft Deletes in Laravel: Fetching, Restoring, and Permanently Deleting Records

Once a model uses SoftDeletes, the default query scope hides deleted rows automatically — these are the specific methods for seeing them again, bringing them back, or removing them for good.

Soft Deletes in Laravel: Fetching, Restoring, and Permanently Deleting Records

Once a model uses the SoftDeletes trait, a "deleted" row isn't actually removed from the database — it's marked with a deleted_at timestamp and automatically excluded from normal query results. Working with those hidden rows again needs a few specific methods.

Setting up soft deletes

use Illuminate\Database\Eloquent\SoftDeletes;

class Post extends Model
{
    use SoftDeletes;
}
Schema::table('posts', function (Blueprint $table) {
    $table->softDeletes(); // adds a nullable deleted_at column
});

Deleting a record (soft delete, once the trait is applied)

$post->delete(); // sets deleted_at instead of removing the row

Normal queries automatically exclude soft-deleted rows

Post::all(); // does NOT include soft-deleted posts

Including soft-deleted records alongside normal ones

Post::withTrashed()->get(); // includes both active and soft-deleted posts

Getting only the soft-deleted records

Post::onlyTrashed()->get(); // only posts with a non-null deleted_at

Restoring a soft-deleted record

$post = Post::onlyTrashed()->find($id);
$post->restore(); // clears deleted_at, making it a normal active record again
Post::onlyTrashed()->where('id', $id)->restore(); // restore directly via the query builder

Permanently deleting a soft-deleted record

$post = Post::onlyTrashed()->find($id);
$post->forceDelete(); // actually removes the row from the database this time

Checking whether a specific instance is soft-deleted

if ($post->trashed()) {
    // this instance currently has a non-null deleted_at
}

Why soft deletes are worth using for certain data

Soft deletes are particularly valuable for data with real business or audit value — orders, user accounts, financial records — where "undo" needs to actually be possible and a permanent, accidental deletion would be a genuine problem, as opposed to something more disposable like a cache entry or a temporary session record, where a hard delete is perfectly reasonable.

A gotcha: unique validation against soft-deleted rows

Rule::unique('users', 'email')->ignore($user->id)->whereNull('deleted_at')

Without whereNull('deleted_at'), a unique validation rule can incorrectly reject a new record's email as "taken" when the only existing match is actually a soft-deleted row — worth remembering specifically because Laravel's default unique validation doesn't account for soft deletes automatically.