Soft-Deletes: Don't Delete, Disappear!
- Michael Modan
- Aug 19, 2024
- 3 min read
Updated: Aug 20, 2024

Are you tired of data vanishing into thin air?
I learned a valuable lesson almost a decade ago that has provided me with a digital safety net ever since: Instead of permanently erasing information, we simply mark it as "gone but not forgotten."
This “soft delete” approach offers a world of benefits for both your team of developers and your business.
Benefits of applying 'soft-delete'
Undo button - Did the user accidentally delete a crucial document? No sweat! With soft deletes, you can easily allow ‘undo’ in your app and bring it back to life.
Detective mode - Picture this: a user claims, "My data vanished!" With soft deletes, you can easily trace and confirm when and why the data was marked as deleted, providing better customer support and reassuring users they're in good hands.
Business Insights - Analyzing deleted data can reveal valuable trends and patterns, giving you deeper insights into user behavior.
Applying this is pretty straightforward, right? Just filter by deletedAt = None.
But what if a developer forgets to add this filter to a new feature? Or what if your model is used in multiple places, like reports? Mistakes happen!
No worries, we’re here to show you a clean and straightforward way to implement this!
Technical implementation with Python and SQLAlchemy
Implementing soft deletes with Python and SQLAlchemy is a breeze. By wrapping the Query class and adding a touch of magic, you can seamlessly filter out deleted records.
No more scattered ‘deletedAt=None’ checks throughout your code!
* Please note the examples below are based on my previous post, “Base Classes: The Unsung Heroes of Your Code”.
Creating a ‘Query’ base class to inject exclusion of 'soft deleted' records
When you call to fetch a record, you’ll notice deleteAt = None is automatically injected into this filter -
This will also work when you query without a filter -
You might be curious about the “if” statement checking len(args).
This is because applying obj.filter_by(deletedAt=None) triggers the query object again,
and re-entering new without any positional arguments helps us maintain the call hierarchy and avoid an infinite loop.
But what if you do want to fetch deleted items? It's as easy as pie.
Implement fetching of ‘deleted’ records
Now, we can invoke the following -
Or with a filter -
The ‘price’ of soft deletes
Soft deletes are fantastic, but even the coolest superheroes have limitations.
Let's take a quick look at the other side of the coin -
Unique keys - occupancy forever?Soft deletes can leave unique keys, like usernames, "occupied" even when the data is gone. While there are workarounds, it's something to keep in mind. Think of it as a reserved parking spot for a ghost car – manageable but not ideal.
Storage space - the soft delete dietOver time, deleted data can pile up and eat into your storage. It's like keeping that old equipment "just in case." We can clean it up eventually, but be mindful of the space it takes.
Cascading references - a manual refinement When you have connected data models (like users and attachments), automatic filtering might not work as expected. Think of it as teaching your app some manners – you need to explicitly tell it how to handle these relationships -
Another helpful tip to prevent data loss is archiving, where deleted records are copied to a separate table before being physically deleted. Like an organized filing cabinet, this keeps your main table clean for active data.
A special thank you note
I’ve dug deep while crafting this elegant approach, and I’d like to share a special shoutout to Miguel Grinberg’s post “Implementing the "Soft Delete" Pattern with Flask and SQLAlchemy” specifically for the clever idea on how to incorporate include_deleted.
This post, dating back to 2016, is 8 years old, which proves that timeless coding principles never go out of style.
WiziWill - a glimpse into my next gig
Speaking of data loss prevention, This blog was written in the context of our current mission to conquer the digital afterlife. We’re simplifying digital inheritance with longevity in mind, just like our code building at WiziWill!
Want to support our mission?
Head over to WiziWill.com. We're still in limited beta but are brewing something awesome.
Give this post a thumbs up.
Let us know your thoughts in the comments below!
By spreading the word (and maybe sending us a virtual coffee!), you'll be helping us build a brighter future for everyone’s digital legacies. Every share and shoutout helps us make the digital world a bit more awesome.
Comments