← all posts

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.