Soft deletes are a trap
·2 min read ·Databases · Laravel · Best Practices
Soft deletes sound like a great idea. Instead of removing a row, you set deleted_at to the current timestamp. You can recover deleted data. You have an audit trail. You never lose anything.
Here's what actually happens.
The Query Pollution
Every query on a soft-deleted table now has an implicit WHERE deleted_at IS NULL. In Laravel, Eloquent adds this automatically—which sounds convenient until it doesn't.
Forget to include soft-deleted records in an admin view? Confused about why a join is missing records? Wrote a raw query that accidentally includes deleted records? These bugs are subtle and annoying.
// This silently excludes deleted users:
$users = User::all();
// You have to explicitly include them:
$users = User::withTrashed()->get();
The Foreign Key Problem
When you soft-delete a user, their posts, comments, and other related records remain. Your database no longer has a consistent state. A post belongs to a user who 'doesn't exist' from most queries' perspective.
When Soft Deletes Are Actually Right
Soft deletes make sense when:
- Users need to be able to undo deletions
- You have regulatory requirements to retain data
- The thing being deleted has significant audit value
For most entities—temporary records, log entries, cached data—they're overkill.
The Alternatives
Archive tables: Move deleted records to a separate deleted_users table. Your main table is clean. Archived data is accessible when you need it.
Event sourcing lite: Store deletion as an event rather than a flag. Reconstruct state from events when you need history.
Hard deletes with backups: For most use cases, a nightly database backup is sufficient disaster recovery. Delete the row. Trust your backups.
Think before you add SoftDeletes. The convenience has costs you'll pay for a long time.