Every software product eventually faces the same quiet disaster. A user clicks delete, realises a second later that they removed the wrong record, and then writes to support in a panic. If your application permanently erased the row, your options are a database backup restore, an awkward apology, or both. Soft delete and undo patterns exist to make this moment boring instead of expensive.
This guide explains how to design soft delete, undo, and a trash or recycle bin in web apps, what it costs in complexity, and where teams usually go wrong. It is written for founders and product owners who want sensible defaults, and for developers who want a checklist before they touch the schema.
With a hard delete, the row is removed from the table. With a soft delete, the row stays, and a field such as deleted_at is filled with a timestamp. Normal queries then filter out anything where that field is set. The record is invisible to users but still recoverable by the system.
Soft delete is a data model decision, not a user interface feature. The interface can then offer three different experiences on top of it: an instant undo toast, a trash page where items wait for 30 days, or an admin-only restore tool. Many teams build only the first one and discover later that support wants the third.
Not every table deserves soft delete. A good rule is to ask what happens if the record vanishes for good. If the answer is "a customer loses work" or "we lose an audit trail", protect it. If the answer is "a temporary session token disappears", hard delete is fine.
Soft delete protects users from mistakes. It does not replace a real erasure process when the law or the customer demands one.
Consider a project management tool for small agencies. A manager archives a client workspace that holds hundreds of tasks, files, and comments. Two days later the client returns and the workspace is needed again. Without soft delete, the team would have to restore a whole database backup just to recover one workspace, which is slow and risks overwriting newer work from everyone else.
With soft delete, the workspace and its children carry a deleted timestamp. A support person opens an admin tool, clicks restore, and everything returns in seconds. The scenario is illustrative, but it is the pattern we see repeatedly when we scope SaaS product builds for clients: the restore feature is cheap to build early and painful to retrofit later.
The most common bug is a report, export, or search index that forgets to exclude deleted rows. Users then see ghost data, or worse, totals that include removed invoices. Central filtering and a few automated tests that insert a deleted row and check every list endpoint will catch this early.
If a customer deletes a tag called "urgent" and later creates a new one with the same name, a plain unique index will reject it. Partial indexes solve this on PostgreSQL. On other databases, you may include the deleted timestamp in the unique key instead.
Rows that are never purged make tables larger and queries slower. Plan a purge window from day one. Thirty days is a common default, though the right number depends on your product and your obligations. Our article on data retention and deletion policies for SaaS covers how to choose it.
Soft deleted data is still personal data. If a user asks you to delete their account, hiding their rows is not enough. Your purge job should have an express path that permanently removes personal information on request, while keeping anonymised records you are legally required to keep, such as invoices.
Many products protect users with a "Are you sure?" dialog. Research in usability circles has long suggested that people click through such dialogs on autopilot. A better pattern is to perform the action immediately and offer an undo for a few seconds. The user feels fast, and mistakes are still recoverable.
Reserve confirmation dialogs for actions that truly cannot be reversed, such as permanently emptying the trash, or those with external side effects, such as sending an email to customers. For those, make the dialog specific: name the thing being removed and describe the consequence in plain language.
The same thinking applies to edits. Version history on important documents, an "undo bulk update" for imports, and a short grace period before an irreversible subscription cancellation all follow the same philosophy: make the common mistake cheap to fix.
For a typical product, a basic soft delete on a handful of core tables with an undo toast could take a few days of developer time, while a polished trash page with cascades, purge jobs, and admin restore might take one to two weeks. These are illustrative ranges, not quotes, because the real effort depends on your schema and how many tables are involved.
The cheaper moment is always at the start. Adding a column and a default scope on a young codebase is simple. Retrofitting it across dozens of queries, exports, and integrations is where the cost balloons.
Teams often ask whether they should use a database feature instead, such as temporal tables or change data capture. Those are valuable for history and analytics, but they are heavier and not designed for a friendly restore button. For most startups, a timestamp column plus a disciplined query layer is the practical choice.
Another frequent question is about files. If a document is soft deleted, its uploaded file must stay in storage until the purge. Make sure your storage lifecycle rules do not remove it earlier, and make sure the purge job removes it later.
Soft delete and undo are small investments with an outsized effect on user trust and support load. Decide which entities need protection, centralise the filtering, handle unique constraints, plan the purge, and treat erasure requests separately. If you are planning a new product or tightening an existing one, our team can help you design these details into the foundation. You can see how we approach such work on our portfolio page, and the pattern will pay for itself the first time a customer says "I deleted the wrong thing".