Skip to content

How Do I Restore My Database If I Accidentally Delete Real User Data?

6 min read

You ran a delete, or a migration, or a "clean up test data" script, and real user rows are gone. The short answer: if your database has automated backups or point-in-time recovery turned on, you restore to a moment just before the mistake. If it doesn't, the data may be unrecoverable, and the first thing to do is stop writing to that database right now. Every new write makes recovery harder.

Why this happens

A vibe-coded app usually talks to its database with a key that can do anything: delete rows, drop tables, truncate everything. That is fine until the day you, or your agent, run one command against the wrong environment. A DELETE without a WHERE clause, a migration that recreates a table, a script that "resets" data you thought was test data but wasn't.

The agent built exactly what you asked. Nobody built the guardrail you didn't ask for: a confirmed backup, a separate staging database, or a rule that stops a destructive command running against production. So the delete works instantly and permanently, the same way it worked in the demo.

How to check

Before you can restore, find out what recovery you actually have.

  1. Stop the bleeding. If users are still writing to the database, every new row overwrites your window for a clean restore. Put the app in maintenance mode or pause writes if you can.
  2. Check whether backups exist. On Supabase, open your project, go to Database > Backups. You will see either daily backups, or Point-in-Time Recovery (PITR) with a time slider, or nothing. On a managed Postgres like Neon or RDS, look for "restore" or "branches" or "snapshots."
  3. Note the exact time of the mistake. PITR restores to a timestamp, so you want the latest moment that is still before the bad command. Check your logs or terminal history for when it ran.

The fix

  1. Do not delete or recreate your project. Restoring almost always happens into the existing project or a fresh copy beside it, not by starting over.
  2. Restore to a point in time (if you have PITR). On Supabase, in Database > Backups > Point in Time, pick a timestamp a minute or two before the delete and start the restore. This rolls the whole database back, so you also lose any good writes made after that timestamp. That is the trade, and it is usually worth it.
  3. If you only have daily backups, restore the most recent one from before the incident. You lose everything since that snapshot, which is why daily-only backups hurt.
  4. Prefer restoring into a copy first. If your platform lets you restore to a new database or branch, do that, confirm the missing rows are present, then copy just the affected table or rows back into production. This avoids throwing away good data unnecessarily.
  5. Verify before you reopen. Query the table and confirm the real user rows are back and counts look right. Only then take the app out of maintenance mode.

The trap to avoid

Do not assume you have a backup because the platform "does backups." An untested backup is not a backup. Free-tier Supabase projects, for example, may not have PITR enabled by default, and a paused project can lose its scheduled backups. The other trap is panicking and running more commands: re-seeding, re-importing, or "fixing" the data manually. That writes over the exact window a clean restore needs. Stop, check what recovery you have, then act once.

Where this fits

Restoring after a bad delete is survivable when the safety net exists, and brutal when it doesn't. The gap is almost never noticed until the day you need it. The free Readiness Report checks whether your project actually has working, restorable backups before your first users trust it with real data, and the Finishing Pass sets up the missing pieces in order. If you have not turned backups on yet, start with do I need database backups before beta launch, and before your next schema change, read how do I run a database migration without losing data.