The restore finished clean. pg_restore exited 0, every table came back, row counts matched, the app booted. Twenty minutes later the first support message arrived: profile pictures were broken. Then the PDFs. Then every file anyone had ever uploaded.

Nothing about the restore failed. This is how Supabase Storage is built — and it applies equally to the built-in daily backups, to PITR, and to your own pg_dump. None of them include your Storage files.

Your database only stores pointers

Supabase Storage is two systems. The storage.objects table in your Postgres database holds the metadata — bucket, path, timestamps, owner. The actual file bytes live in a separate S3 backend operated by Supabase, entirely outside your database.

Restoring the database restores the pointers. If the files behind them were deleted, or you're restoring into a fresh project, those pointers now reference objects that don't exist. The dangerous part: everything looks healthy from SQL. Row counts match, constraints hold, select count(*) from storage.objects is exactly right — until something requests a file and gets a 404.