Supabase restores don’t bring back Storage files

You restore a backup. Every table is back, every query works — and every avatar, upload, and document is a 404. This is not a bug in your restore; it is how Supabase Storage is built, and it applies to the built-in backups, PITR, and your own pg_dump equally.

How Storage actually works

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, outside your database entirely. The database rows are pointers, not files.

Restoring the database restores the pointers. If the files behind them were deleted, or you are restoring into a fresh project, the pointers now reference objects that do not exist. The dangerous part is that everything looks healthy from SQL — row counts match, constraints hold — right up until something requests a file.

See it yourself

Ask your database what it thinks is in Storage:

select bucket_id, name, created_at
from storage.objects
order by created_at desc
limit 20;

Now dump the database with pg_dump and restore it into a local Postgres or a fresh project. Run the same query: the rows are all there. select count(*) from storage.objectsmatches the original exactly. But none of the files those rows describe came with the dump — every URL they resolve to is a 404. The restore passes every SQL check you can write and still lost your users’ data.

How to actually back the files up

Supabase exposes the Storage backend over an S3-compatible endpoint, which means the files can be copied out like any other bucket. In the dashboard: Storage → Settings → S3 Access Keys, create a key pair. The endpoint is:

https://<project-ref>.storage.supabase.co/storage/v1/s3

Option 1: the open-source CLI

The backupdrill CLI (MIT, github.com/backupdrill/cli) snapshots the database and the Storage files in one run, streaming both to a bucket you own:

npm install -g backupdrill

export BACKUPDRILL_SUPABASE_STORAGE_ENDPOINT="https://<ref>.storage.supabase.co/storage/v1/s3"
export BACKUPDRILL_SUPABASE_STORAGE_REGION="<project-region>"
export BACKUPDRILL_SUPABASE_STORAGE_ACCESS_KEY_ID="…"
export BACKUPDRILL_SUPABASE_STORAGE_SECRET_ACCESS_KEY="…"

backupdrill backup

You will also need the database connection string and a destination bucket — the CLI docs cover the full configuration (including the egress cost of pulling files out), and the bucket setup guide walks through creating a destination on R2, S3, or B2.

Option 2: the hosted service

BackupDrill runs the same engine on a schedule: each snapshot captures the database and the Storage files together, with a per-file checksum recorded in the manifest — so a restore can prove every file came back intact, not just that the metadata rows returned. Scheduled restore drills (on paid plans) then verify the latest snapshot actually restores, and failures are emailed. Setup takes a few minutes via the quickstart; the bad-day path is documented in restore & recovery.

The takeaway

Whatever you use for the database — built-in backups, PITR, or your own dumps — none of it covers Storage files. If users upload anything you would mind losing, the files need their own backup path. For the wider decision, see every Supabase backup option compared.

Sources

Facts and prices last verified July 11, 2026 against the sources above. Written by the team behind BackupDrill.

Database and Storage files, backed up together into your own bucket, with drills that prove they restore. Open the console — the free plan covers one project.