Joe Ashwell launched UnwindHR on Firebase. He already had three products on Supabase. He picked Firebase anyway because he wanted the HR tool out the door.
A few paying clients later, the reads looked wrong. Nested Firestore collections meant one screen could fan out into many document fetches. He also wanted SQL and cleaner backups, and he was tired of feeling locked into Google.
He moved. The cutover was a weekend. The prep took about two weeks part-time.
If you are looking at the same route, start with the Firebase → Supabase playbook. Joe’s DEV write-up is the field report behind this post.
Why he left
UnwindHR started as freelance work and turned into a SaaS. Auth, Firestore, Storage, and Functions were all on Firebase.
The product had roles and permissions. Those lived in nested collections. Joe says that design, not Firebase itself, produced the high read counts. Still, the longer he stayed, the harder a move would get.
He wanted a SQL mental model. He did not want to run Firebase and Supabase side by side. An older dual-write guide he had read felt like overkill for his user count and database size.
The plan
He wrote the sequence down before he touched production:
- Take a Firebase snapshot.
- Design Postgres tables and policies.
- Refactor the app against Supabase.
- Test.
- Take UnwindHR down for a weekend.
- Export the latest Firebase data and import it.
- Test again.
- Point the product at Supabase.
That last snapshot only happens after the schema and the code already work. I like this order. The weekend is only for the copy and the checks.
Auth
He followed Supabase’s Firebase Auth guide. Users moved. Passwords and original user IDs did not.
He tried more than one import path. Same result. He decided password resets were acceptable. For the lost IDs, he added an optional fb_id column on most tables so he could still join back to Firebase records.
If you migrate a live SaaS, plan that identity gap before cutover. Tell users they will reset passwords. Keep a mapping column until every foreign key has been rewritten.
Firestore
This is where the official tooling fell over.
Supabase’s Firestore data guide and the official export package returned top-level collections only. Joe had nested collections. Those never showed up.
He exported the whole database to one JSON file with node-firestore-import-export. Then he wrote a script that walked __collections__ and wrote one JSON file per collection, flattening nested names with underscores.
Each collection became one table. Column names, types, and IDs needed hand edits. id became fb_id.
He only had a few hundred rows per table, so he loaded JSON through the Supabase SQL editor with json_array_elements. Parent tables went first, user_profiles and the like. Then a local script rewrote child documents to the new Postgres IDs before those rows went in.
A nested document store does not become relational because you exported it. You design the tables, then you transform.
Storage
The official Storage download path corrupted files. PDFs came back as unreadable binary. JPEGs and PNGs were fine.
Joe installed the Google Cloud SDK, ran gcloud init, and copied the bucket with gsutil -m cp -r. About 200 folders and files landed on disk. He dragged that tree into the Supabase dashboard. The upload worked.
When the documented helper fails, the Cloud SDK is the fallback. Compare a sample of PDFs, images, and whatever else customers actually open.
What he gained
After the weekend, UnwindHR was on Postgres, Supabase Auth, and Supabase Storage. He no longer needed the Firebase Functions he had been running.
He called the move the right decision. The work took two weeks part-time plus the weekend outage. He did not keep both backends live.
He accepted password resets and new user IDs. He also accepted a short maintenance window. He refused a long dual-write period, and he refused a one-shot export of nested data he could not inspect.
What I would copy
Design the schema before the last export. Rehearse the app against empty or sample tables. Budget for password resets and an ID map.
If you have subcollections, do not stop at a top-level Firestore export. Split the dump, load parents, then rewrite children.
For files, test PDFs as well as images. If the helper mangles them, pull the bucket with gsutil and upload from disk.
The Firebase → Supabase playbook covers the same surfaces Joe moved: Auth, Firestore, Storage, rules, functions, and clients. Use his write-up for the gotchas the docs skip.