Soft Deletes in Laravel: When to Use Them and When They're a Trap
Soft deletes are one of those Laravel features I've seen on almost every project I've inherited. And honestly? They're on a fair few projects I built myself, in my earlier years.
The idea is appealing. Instead of permanently removing a record, you just stamp a deleted_at timestamp on it and Laravel quietly filters it out of your queries. Simple. Safe. Recoverable. What's not to like?
Quite a lot, as it turns out — at least when they're used without much thought. After 25 years of building with PHP and several years deep into Laravel SaaS products, I've developed a much more cautious relationship with SoftDeletes. Let me walk you through how I think about them now.
The Appeal Is Real
Let's be fair — soft deletes genuinely earn their place in certain situations. If you're building something where users can accidentally delete records and need a way to recover them, soft deletes are a solid safety net. Think of a CRM where a sales rep deletes a contact by mistake, or a project management tool where someone archives a task they still need.
In those cases, the restore() method and the withTrashed() scope are genuinely useful tools. Laravel's implementation is clean and the Eloquent integration is seamless.
But the problem I've seen — and caused — is treating soft deletes as a default rather than a deliberate choice.
The Quiet Problems They Introduce
1. Your queries get silently wrong
Once you add SoftDeletes to a model, every query on that table automatically excludes deleted records. That sounds convenient, but it also means you can introduce subtle bugs without realising it.
Imagine you've soft deleted a user, but their records in a related table — orders, subscriptions, notes — still exist and still reference them. Depending on how your relationships are set up, you might get unexpected nulls, broken joins, or missing data in reports. The deleted user is invisible to Eloquent, but their footprint is still very much there.
I've spent hours debugging data issues that boiled down to a soft-deleted pivot record quietly excluding results from an eager load.
2. Your database bloats over time
In a SaaS app with real users, data accumulates fast. Soft deleted records don't go anywhere — they just sit in your tables with a timestamp, forever, unless you build a cleanup job to purge them.
This matters for performance. Indexes have to account for all rows, not just the active ones. If 40% of your users table is soft deleted, your queries are working harder than they need to. I've seen this bite teams who were proud of their query times in staging, only to find production grinding as the deleted record count crept up.
3. Unique constraints become a nightmare
This is probably the most common foot-gun. Say you have a unique constraint on email in your users table. User signs up, later deletes their account (soft deleted), then tries to sign up again with the same email. Boom — unique constraint violation, because the old soft-deleted record is still sitting there.
You can work around this — people often move the unique check into application code or use a composite index — but now you've added complexity to solve a problem that soft deletes created.
4. It complicates GDPR compliance
This one matters a lot for SaaS builders. Under GDPR, when a user asks you to delete their data, you actually need to delete it. Soft deletes can give you a false sense of security here. You mark the record as deleted, but the personal data — name, email, usage history — is still sitting in your database.
If you're relying on soft deletes as your deletion mechanism, you're probably not compliant. You need a proper data removal strategy regardless, which makes you question why you soft deleted in the first place.
When I Do Still Use Them
I haven't abandoned soft deletes entirely. There are specific cases where I reach for them deliberately:
Audit trails and recoverability — When the business logic genuinely requires the ability to restore records, and the team understands the trade-offs, soft deletes make sense. A content management system where editors might unpublish articles is a good fit.
Short-lived tombstoning — Sometimes I want to soft delete a record, trigger some cleanup logic via a queued job, and then hard delete it shortly after. The soft delete is a transitional state, not a permanent one. I'll pair this with a scheduled command that purges records older than a set threshold.
When you're auditing changes — Though honestly, if auditing is your primary concern, I'd point you towards a proper audit log package (Spatie's laravel-activitylog is what I use) rather than soft deletes.
What I Do Instead
For most models in a SaaS app, I now default to not using soft deletes, and instead think carefully about what deletion actually means for that entity.
For users, I'll often have a status field — active, suspended, deactivated — rather than deleting the row at all. This gives me more expressive state management and keeps the record around for billing history, audit purposes, or support queries, without the soft delete baggage.
For things like subscription plans, products, or pricing tiers that you can't delete because historical records reference them, I'll use an archived_at timestamp and a named scope. It's functionally similar to soft deletes, but it's explicit in the domain model. An archived plan is different from a deleted plan, and the name should say so.
For everything else — temporary data, log entries, records that genuinely should be removed — I just delete them. Hard. With a foreign key constraint to enforce referential integrity, and a database transaction to make sure related records are handled correctly.
A Quick Rule of Thumb
Before I add SoftDeletes to a model now, I ask myself three questions:
- Does the business logic require the ability to restore this record?
- Do I have a strategy for actually purging soft deleted records over time?
- Have I accounted for unique constraints and GDPR obligations?
If the answer to question one is no, I don't use soft deletes. If the answers to two and three are unclear, I have a conversation before reaching for the trait.
The Bigger Lesson
Soft deletes are a great example of a feature that feels like a sensible default but is actually a deliberate trade-off. Laravel makes it easy to add — just a trait and a migration — but that ease can mask the implications.
The best codebases I've worked on treat database design decisions like this with the same care they'd give to architecture decisions. Because in a long-lived SaaS app, your data model is your foundation. Get it wrong and everything built on top wobbles.
Next time you're reaching for SoftDeletes, just pause for a moment. Ask whether it's the right tool, or just the convenient one. Sometimes those are the same thing. Often, they're not.