Soft Delete and Undo Patterns for Web Apps: A Practical Guide

Soft Delete and Undo Patterns for Web Apps: A Practical Guide — cover image

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.

What soft delete actually means

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.

When you need it and when you do not

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.

A real-world example

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.

Step-by-step: implementing soft delete well

  1. Choose the tables. List the entities users can remove. Mark each as soft, hard, or soft-then-purge.
  2. Add the column. Use a nullable timestamp such as deleted_at, and optionally deleted_by so you know who acted. A timestamp is more useful than a boolean because it powers the purge schedule.
  3. Centralise filtering. Do not rely on every developer remembering a filter. Use a default scope, a database view, or a repository layer so that deleted rows are excluded everywhere by default.
  4. Handle unique constraints. If emails or slugs must be unique, a deleted row can block a new one. Use partial unique indexes that only apply where deleted_at is null.
  5. Cascade deliberately. Decide what happens to child records. Deleting a project should usually hide its tasks too, and restoring should bring the same set back, not unrelated tasks deleted earlier.
  6. Build the restore path. Provide an undo toast for the immediate case and a trash view for the longer window.
  7. Schedule the purge. A background job permanently removes rows older than your retention window. Running it reliably is a job for a queue, as we discuss in our guide to background jobs and queues for web apps.
  8. Log the action. Record deletes and restores in an audit trail so disputes can be resolved with facts.

The hidden traps

Forgotten filters

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.

Unique constraints and foreign keys

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.

Growing tables

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.

Privacy and erasure requests

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.

Undo versus confirmation dialogs

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.

Undo for more than deletes

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.

Key benefits for your business

What it costs

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.

Testing checklist before launch

Common questions founders ask

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.

Conclusion

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".

Frequently Asked Questions

What is soft delete?
Soft delete marks a record as deleted with a timestamp instead of removing the row. Normal queries hide it, but the data can be restored later.
Should every table use soft delete?
No. Protect valuable business data such as projects, customers, and invoices. Sessions, tokens, and cache rows can be hard deleted.
How does soft delete affect unique fields?
A deleted row can block a new one with the same value. Use partial unique indexes that apply only where the deleted timestamp is empty.
How long should deleted data be kept?
Thirty days is a common default, but the right period depends on your product, customers, and legal obligations.
Does soft delete satisfy erasure requests?
No. Soft deleted data is still personal data, so you need a path that permanently removes it when a valid erasure request arrives.