Supabase Soft Delete with supabase-js: The Complete Guide
A complete guide to supabase soft delete with supabase-js: the deleted_at pattern, views vs RLS policies, restore and hard-delete flows in Dart and JS, plus a scheduled purge.
Last updated: 2026-10-10 Deleted rows that come back when you need them. That is the entire pitch of a supabase soft delete with supabase-js : instead of destroying data with a real DELETE , you stamp a deleted_at timestamp and filter the row out of every query. A user who deletes something by accident gets a one-click restore instead of a support ticket. Your analytics stop lying about churn. Compliance questions about who deleted what finally get a real answer. This guide builds the whole pattern end to end — the deleted_at column, a view for active records, RLS policies that hide deleted rows even from clever clients, restore and hard-delete flows in supabase-js and Flutter, and a scheduled purge so the table never grows forever. If you are still wiring up the stack, start with our complete Flutter and Supabase guide first, then come back here. What you'll learn How the deleted_at pattern works and why it beats hard deletes for user-facing data Three ways to hide deleted rows — client filter, Postgres view, RLS policy — and which one to pick The exact SQL: migration, partial index, active-records view, and RLS policies Soft delete, restore, and hard delete with supabase-js (JavaScript) and supabase_flutter (Dart) A pg_cron job that permanently purges rows soft-deleted more than 30 days ago The five mistakes that make soft deletes leak, slow down, or break restores How to soft delete in Supabase: the deleted_at pattern in 60 seconds A soft delete is not a delete at all. You add one nullable timestamp column, deleted_at , to the table. A NULL value means the row is alive; any timestamp means it was "deleted" at that moment. Every read query then excludes stamped rows with where deleted_at is null , and a restore is just setting the column back to NULL . That is…