How to move your app off Lovable
Written by Claude for GannSystems, in the manner of an engineering blog. Randall Gann read it before it went up.
An app built on Lovable is a React frontend, a Supabase backend, and a chat window that edits both. Lovable hosts the frontend, written in TypeScript. The backend runs on Lovable Cloud, which is a Supabase project that Lovable manages on your behalf: Postgres for data, Supabase Auth for logins, Storage for uploads, and Edge Functions for server-side code. There is no custom server in between. The browser talks to Supabase directly, and Row Level Security policies in the database decide which rows each logged-in user can see.
That arrangement is what makes a migration possible without taking the app down. Postgres exports to Postgres. Supabase is open source, and a project you create yourself runs the same auth, storage, and function runtime. Nothing in the stack has to be rewritten. It has to be copied, checked, and reconnected.
In this post, we explain when a migration is worth doing, the three paths off Lovable, and the six phases that move a live app in the order that keeps the old one running until the new one has passed every check. Much of the detail comes from Carol Monroe's Lovable Cloud migration playbook, run end to end on a real project in 2026.
Three buttons that may be all you need
Lovable ships three controls under Cloud > Overview > Advanced settings, and two of them cover the most common reasons people think they have to leave:
-
Pause turns off a project you are not using.
-
Export project data produces a full database backup you can download and keep. It runs at most once every 24 hours, and you keep building while it does.
-
Remove deletes Lovable Cloud from the project. It is the last step of a migration, not the first.
A backup or a pause does not need a migration. What does need one is a requirement that only a Supabase project or a host you control can meet:
| You need | Why Lovable Cloud cannot give it to you |
|---|---|
| Login and reset emails from your own domain | Cloud sends them from Lovable's address |
| The full Supabase dashboard | The SQL editor, extensions, logs, and monitoring live there |
| The service role key | Admin tasks and certain server-side functions require it |
| Separate staging and production | Cloud is one database per project |
| The bill in your name | Your organization, your billing, your account |
| A developer or a second tool on the code | They work in the repo without the Lovable chat |
If none of those rows is yours, stop here and take the export.
Three paths, each a step further
The paths nest. Each includes the one before it, and you can stop at any of them.
Path 1: take the code, keep the backend. Connect GitHub, host the frontend wherever you like, and leave Lovable Cloud as the database. This is the smallest change, for when the backend is fine and you want the code under your own control.
Path 2: move the backend to your own Supabase. Export the database, restore it into a Supabase project you own, and connect that project back to Lovable. You keep the chat for building; the data, the users, and the keys are yours. This is the path for custom auth emails, the service role key, the full dashboard, or a staging database.
Path 3: leave Lovable. Do Path 2, then host the frontend yourself and work in a normal editor with pull requests, tests, CI, and preview deploys. This is the path when a developer or a team is taking the code over.
Phases 1 through 6 are Path 2, because that is where the risk to your users lives. Paths 1 and 3 add the hosting section at the end.
Rule one: nothing is removed until its replacement works
Until the final step, the original keeps serving traffic, untouched. The new project is built beside it, tested against the inventory, and only then connected. This is the same discipline as a blue-green deployment: the old environment takes traffic until the new one is proven, and the switch is the last thing that happens. The order is fixed:
- Export the database.
- Download the export and the storage files.
- Build the new Supabase project: restore, repair, upload, deploy.
- Test a login on the new project.
- Verify that every count matches the inventory.
- Remove Lovable Cloud.
- Connect your own Supabase to the Lovable project.
Steps 1 through 5 can be discarded and repeated. Step 6 cannot be undone, which is why it is sixth.
Phase 1: the code goes to GitHub
In the Lovable editor, connect the project through the GitHub integration. Lovable pushes the full project to a repository you own and keeps it synced in both directions from then on. Clone it and open the supabase/ folder:
git clone https://github.com/your-name/your-app.git
cd your-app
ls supabase/
Three things in that folder matter for the move:
-
migrations/holds a timestamped SQL file for every table, policy, function, and trigger the builder created. -
functions/holds the source of each Edge Function. -
config.tomlholds per-function settings, includingverify_jwt, which controls whether a function requires a logged-in caller.
Before going further, take the inventory: the number of tables, users, storage files, Edge Functions, cron jobs, and secrets the app has today. Phase 5 compares the new project against these counts.
Phase 2: export and download everything
Export
Go to Cloud > Overview > Advanced settings > Export project data and click Export data. Rather than waiting for the email, open Cloud > Storage after about a minute and look for a new bucket named like database_export_03_10_26. The export is a .zip inside it.
The file is a standard pg_dump backup: schema, data, RLS policies, triggers, functions, and sequences. The cap is 5 GB, one export per day; a database above the cap needs a request to Lovable support with the project ID.
Download first
The export is stored inside Lovable Cloud's own storage, so removing Cloud deletes the export along with everything else. Download the zip, unzip it, and confirm the archive reads:
pg_restore --list your-export.backup | head -50
pg_restore --list your-export.backup | grep -c "TABLE DATA"
A complaint about zstd compression means the local Postgres tools are too old or were built without zstd. On macOS, brew install postgresql@18 and call that pg_restore by its full path.
Download the storage files
The database export contains the list of files in Storage, not the files. Create one local folder per bucket, keep the bucket's names and subfolders, and skip the database_export_* bucket. A few files can be pulled from the Storage tab by hand; hundreds should be scripted.
Phase 3: build the new home
Create the project and get the right connection string
Create a project at supabase.com in a region near your users and store the database password. In the dashboard, click Connect and copy the Session pooler string, which has this shape:
postgresql://postgres.[project-ref]:[password]@[pooler-host]:5432/postgres
The session pooler is the one to use. The direct connection is IPv6 only, and many home internet connections cannot reach it. A password containing special characters has to be URL-encoded (an exclamation mark becomes %21).
Restore
pg_restore --no-owner --no-privileges \
-d "postgresql://postgres.[project-ref]:[password]@[pooler-host]:5432/postgres" \
your-export.backup 2> restore-errors.log
The restore prints a long run of errors. The new project already has its own auth, storage, and cron schemas, so every attempt in the backup to recreate them fails with "already exists" or "permission denied" while the data loads around them. The error log is not the signal; the row counts are.
Check the users
select
(select count(*) from auth.users) as users,
(select count(*) from auth.identities) as identities;
When identities comes back lower than users, the restore loaded the two tables in the wrong order and the foreign key rejected part of the second one. A second pass on that one table fixes it:
pg_restore --data-only -n auth -t identities \
-d "<connection string>" your-export.backup
Both numbers should match each other and the Phase 1 inventory.
Phase 4: what the restore cannot carry
Six things in a live app point outside the database. Each needs a hand.
Passwords. Lovable's documentation states that user passwords do not survive the export in a usable form, so every email-and-password user will set a new one. Configure password recovery on the new project, test it end to end, and email users before the switch rather than after. Google and magic-link sign-ins are unaffected.
Cron jobs. Scheduled jobs are in the backup, but the restore has no permission to write them. Enable pg_cron under Database > Extensions and recreate each one:
select cron.schedule('nightly-cleanup', '0 3 * * *', $$ delete from carts where updated_at < now() - interval '7 days' $$);
Then find any job whose command calls a URL on the old project, because it will fail silently on every run:
select jobid, jobname, command from cron.job where command like '%supabase.co%';
Storage files. The restore created rows describing files that do not exist yet, and those rows block uploads with "resource already exists." Remove them through the Storage dashboard or the CLI rather than SQL, delete the database_export_* bucket, then upload:
supabase storage cp --recursive ./avatars ss:///avatars -p <project-ref>
Load one image in a browser to confirm it is served.
Old URLs in data. Any column that stores a full URL still carries the old project reference. Rewrite it:
update products
set image_url = replace(image_url, '<old-ref>', '<new-ref>')
where image_url like '%<old-ref>%';
Signed URLs, the ones ending in ?token=, are the exception. They are signed with the old project's keys, so a rewritten one looks valid and returns an error. Null them and let the app issue new ones.
Edge Functions and secrets. Function code is in the repo, so deployment is one command:
supabase functions deploy --project-ref <new-ref> --workdir .
Secret values do not travel. The Stripe key, the email provider key, and anything else the functions read must be re-entered under Edge Functions > Secrets with the same names. Leave every SUPABASE_* key out; the new project supplies its own.
Lovable AI features. Calls to Lovable's AI gateway use LOVABLE_API_KEY, a workspace secret that survives removing Cloud but is not readable from your own functions automatically. On Path 2, ask the Lovable agent to move those calls into Lovable server functions, or copy the key into the function secrets. On Path 3, replace the gateway with a direct call to an AI provider under your own key.
Phase 5: the gate
Compare the new project against the Phase 1 inventory, line by line:
| Check | Old | New |
|---|---|---|
| Tables and row counts | ||
| Users and identities | ||
| Storage files per bucket | ||
| Edge Functions | ||
| Cron jobs | ||
| Secrets re-entered | ||
| Rows still pointing at the old project | 0 |
Then do three things a user would do: reset the password on a migrated account and sign in with it, open an uploaded image, and trigger one Edge Function such as a test checkout. Any mismatch means stop and fix; Lovable Cloud is still serving and nothing is lost. A green gate can sit for days.
The backup is frozen at the moment of export, so anything users did after that (orders, signups, edits) is absent from the new database. Export again right before the switch, or schedule a short quiet window.
Phase 6: flip the switch
Remove Lovable Cloud
Cloud > Overview > Advanced settings > Remove Lovable Cloud asks for two checkboxes and the project name, then deletes the Cloud database and its storage, including any export still sitting in it.
Connect your own Supabase
With Cloud gone, the Cloud tab offers to connect an existing Supabase project. Authorize your organization, choose the new project, and Lovable rewrites .env with the new URL and key.
The integration can also replace the Supabase client file with its template, which breaks the build on some newer server-rendered projects. An error page right after connecting is that, not data loss. The fix is a narrow prompt to the Lovable agent:
Check my Supabase connection. Confirm the app targets my new Supabase project
and that auth, queries, storage, and edge functions work against it. List
anything still pointing at the old backend.
The tables, users, storage files, and edge functions ALREADY EXIST in the new
project. Do NOT create tables, run migrations, seed data, or rewrite files
beyond the connection wiring.
The prohibitions carry the weight. An open-ended "fix the connection" can send the agent into rerunning migrations against live data.
Final tidy
-
Recreate Google and any other social provider in the new project with the callback
https://<new-ref>.supabase.co/auth/v1/callback, and update it in the provider's console. -
Set the Site URL and redirect URLs under Authentication > URL Configuration.
-
Delete the
.backupfile. It contains your users' personal data. -
Rotate the database password if it was ever pasted into a chat, a script, or an AI tool.
-
Delete temporary helper functions in both the repo and Supabase. The sync runs both ways, so a helper deleted on one side returns from the other.
Path 2 is done. The app runs on a Supabase project you own, and the Lovable chat still builds against it.
Hosting the frontend yourself
Paths 1 and 3 need a host for the frontend. Lovable projects are ordinary React apps, so Vercel, Netlify, Cloudflare Pages, or a server of your own all work. Most are single-page apps built with Vite:
npm install
npm run build # static output in dist/
A static host serves dist/ as is, with one rewrite rule so every path returns index.html; without it, a refresh on any page but the home page is a 404. Projects whose package.json lists TanStack Start or another server-rendering framework need a host that runs Node.
Set the environment variables from .env, usually VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY. Anything prefixed VITE_ ships to the browser, so the service role key never goes there. Then point the domain's DNS at the new host, wait for the certificate, and update the Site URL in Supabase Auth so reset links land on the right domain.
Your first week on your own
Lovable was handling four habits silently. Each one now belongs to you.
-
Two projects.
yourapp-devandyourapp-prod. Break things in dev; never test against customers. -
Migrations as save points. Every schema change is a file in
supabase/migrations/, committed, applied to dev first and production second. -
Backups you have restored. Enable daily backups or Point in Time Recovery, and once a month restore one into a scratch project to prove it works.
-
RLS on every table. Confirm it is on everywhere, then sign in as an ordinary user and try to read another user's rows.
Trade-offs
-
Path 3 gives up the chat. Off Lovable, a change is an edit, a commit, and a deploy rather than a prompt. Many teams keep Lovable for prototypes and run production separately.
-
Cost. A small Supabase project runs free or close to it; production apps usually start on the Pro plan, from $25 a month per organization. Frontend hosting for a small app is often free.
What we would tell anyone doing this
- Inventory before anything else, and let the counts, not the error log, decide whether the restore worked.
- Download the export before removing anything, because the export lives inside the thing being removed.
- Gate the cutover on a matching inventory and a real login, and sit at a green gate as long as you need to.
Nothing in a Lovable app is proprietary, so moving it is a copy, a check, and a reconnect rather than a rewrite. GannSystems does this migration for people who would rather not run it themselves: we take the inventory, do the move, and hand back the keys, with the price quoted before any work starts. If you want that, ask through gannsystems.pro/contact.
Acknowledgements
Written by Claude for GannSystems. Randall Gann read it before it went up.
Ready to implement ML in your business?
Contact our team for personalized guidance and implementation support.
Get Started Today