You have shipped several Lovable apps for clients, and one client is now asking when to migrate away from Lovable. The harder question is whether this app should move, when, and what a wrong call will cost your agency.
Use this five-criterion matrix to score a project in one working session. It gives you a written decision and the reason behind it.
Moving too early can create an unnecessary fixed-fee project. Moving too late can reveal a load-bearing webhook receiver halfway through cutover. App Rescue Desk is independent and is not affiliated with Lovable, Supabase or Stripe.
TL;DR
- User count alone does not describe migration cost: a 200-user app taking live payments through four webhook endpoints can be a harder move than a 20,000-user app with a thin schema and no side effects.
- Five factors map more directly to the work and operational exposure: whether money moves, the category of data held, inbound webhooks with side effects, where users arrive from, and who gets paged when the app breaks.
- Score those five on stakes and coupling to get one of four answers: stay, instrument and wait, move one component, or use a staged cutover with rehearsal.
Why the tempting user-count trigger misleads
User count measures traffic, not the surface you must move.
As documented and checked in 2026-08, a Lovable-synced repository contains code and migration files, not database data. Each project links to one repository, and an existing repository cannot be imported through that integration.
A push updates the editor preview. The live site changes only when you publish from Lovable.
Lovable’s secrets documentation, checked 2026-08, distinguishes the built-in backend from an external Supabase connection. Built-in backend secrets are write-only in Lovable’s Cloud secrets view. When a project uses external Supabase, secrets are managed in Supabase.
In both cases, backend secrets are injected into edge functions or server-side integrations rather than exposed in frontend code. VITE_ values are different: they are browser-exposed build-time values stored in the project’s .env file and may be present in the synced repository.
That inventory exists at 10 users and at 10,000. A quiet app can acquire a revenue path and several webhook receivers while its user graph stays flat. If nobody re-scores it, the agency may find the coupling during cutover.
In the other direction, an agency may move a growing but stateless form tool despite having little architecture to untangle. The work still lands inside a fixed fee.
Holding the repository is valuable, but it does not make the repository a complete migration package. Capability limits—background workers, cron, CDN control, or unit cost at scale—are a separate product-side question.
This matrix scores migration cost and operational risk, not whether Lovable can support the next feature. Ask what the app is on the hook for, not how big it is.
Five criteria estimate the migration work
Three criteria feed Stakes: revenue, data, and liability. Two feed Coupling: webhooks and acquisition. These thresholds are our operational defaults, not industry values. Each criterion explains why we draw its line.
Recent revenue requires a rollback point
Our default is any live-mode charge in the last 30 days. Configured but dormant credentials do not show active traffic. A charge last week means a cutover failure can be visible to a paying customer during the planned window.
That is our sequencing judgment, not a claim about Stripe’s charge or refund lifecycle. When revenue is active, write the rollback point before the window opens. Revenue adds one Stakes point.
Data category changes the policy surface
Classify the data as none, personal, or regulated. Category matters more than row count because the policy surface changes with what the records mean.
In Supabase’s documented behavior, checked 2026-08, enabling row-level security (RLS)—the Postgres rules governing which rows a user can access—without policies prevents API access with a publishable key. The Supabase RLS guide establishes that access depends on grants, policy state, and request role, not data volume.
This statement does not cover direct database connections, secret or service-role access that bypasses RLS, or views that need their own review.
Our judgment is that regulated data requires the target project’s policies to be re-verified, not assumed to have moved correctly.
On a project you are authorised to manage, list each application table with its data category, RLS state, grants, and applicable policies. The review fails if a personal or regulated table exposed to the application lacks a reviewed access rule.
An enabled indicator alone is not a pass. Role behavior still needs testing.
If this is the central problem, the RLS and data-isolation review covers that separate scope. Personal data adds one Stakes point; regulated data adds two, capped at two.
Side-effecting webhooks add cutover operations
We draw the high-coupling line at more than three inbound webhook endpoints with side effects. One to three can be tracked directly during a cutover. Above three, the inventory must carry the load instead of memory.
A receiver counts if it writes a row, sends mail, grants access, or causes another external action. A health ping does not.
Stripe documents a unique signing secret for each endpoint and live-mode delivery attempts for up to three days, using exponential backoff; this was checked 2026-08. It generates a new signature and timestamp for each delivery attempt.
The retry window gives you recovery time, but it does not prove that a receiver was re-pointed or that its side effect completed. Monitor the endpoint’s delivery status and the resulting application state during cutover.
Match this exact message against your authorised application’s logs:
Webhook signature verification failed. Err: No signatures found matching the expected signature for payload.
The Stripe signature guide, checked 2026-08, documents the message. It proves signature verification failed, not why. Stripe says the request body, signature header, or endpoint secret may be wrong.
One to three receivers add one Coupling point. More than three add two.
Organic acquisition makes the host a dependency
Our default line is more than 30% of signups from organic search on an indexed custom domain. Above it, treat the host as a customer-acquisition dependency whose change needs an indexing and verification plan. This is our planning threshold, not a ranking forecast.
Lovable’s docs, checked 2026-08, say a custom domain adds your own host for a published project. It does not remove the project’s *.lovable.app URL.
Their SEO guidance says a domain change requires rerunning the SEO review and re-verifying Google Search Console for the new domain. The checks include indexing, canonical URLs, sitemap host consistency, and Search Console setup.
Those checks belong in a domain-change plan.
If you lack analytics access, do not guess. Leave the criterion unscored and return Not yet — instrument first, regardless of the other total. An unknown is not zero. A measured share above the line adds one Coupling point.
Contracts and account ownership place operational exposure
Ask whether the engagement includes uptime or response-time language, and whether your agency still holds a Lovable seat. A yes to either puts operational exposure on your agency under this framework, adding one Stakes point.
For an externally hosted frontend, Lovable documents that VITE_ variables are embedded at build time, so a change requires a rebuild. When the backend and data also move outside Lovable, auth providers and API credentials require manual reconfiguration.
Its scope statement is direct: “Lovable cannot monitor or debug production infrastructure it does not control.” These behaviors were checked in the external deployment documentation in 2026-08.
Self-hosting changes the standing work after cutover as well as the migration itself.
Score stakes and coupling separately
Score the five answers, then read one cell. More than three webhooks reaches Coupling high by itself. If acquisition is unscored because analytics are unavailable, use Not yet — instrument first rather than zero.
Stakes
- Revenue: +1 for a live-mode charge in the last 30 days.
- Data: +1 personal or +2 regulated, capped at 2.
- Liability: +1 for uptime/response-time language or an agency-held Lovable seat.
- Bands: Low 0 · Medium 1–2 · High 3+.
Coupling
- Webhooks: +1 for one to three endpoints with side effects; +2 for more than three.
- Acquisition: +1 when more than 30% of signups are organic on an indexed custom domain.
- Bands: Low 0 · Medium 1 · High 2–3.
| Coupling low | Coupling medium | Coupling high | |
|---|---|---|---|
| Stakes low | Stay. Record the decision and a re-score trigger. | Stay. List what lives outside the repo; move nothing. | Not yet — instrument first. Enumerate live webhook endpoints and their effects. |
| Stakes medium | Partial. Move the component carrying the stakes. | Not yet — instrument first. Check whether the repo remains the source of truth. | Staged cutover. Rehearse identity migration on a copy project before quoting. |
| Stakes high | Partial. Move the component carrying the stakes and rehearse it. | Staged cutover. Write the rollback point, then book the window. | Staged cutover. Sequence data → identity → webhooks → domain, rehearsing each on a copy. |
The matrix returns a band, not a safety verdict
The matrix does not assess feature capability, produce a duration, or declare a migration safe. It returns a decision band and a first action. It also cannot score dependencies nobody has inventoried.
Stored objects, retained history, identity behavior, and production-only configuration can change the work without changing the score.
Score each project with five sourced answers
Copy this card for each project. Support every answer with a dashboard, contract, analytics report, or inventory entry.
- Has the app taken a live-mode charge in the last 30 days? Stakes: 0/1
- What data does it hold: none, personal, or regulated? Stakes: 0/1/2
- Does the engagement include uptime or response-time language, or does the agency hold the Lovable seat? Stakes: 0/1
- How many inbound webhook endpoints have side effects: 0, 1–3, or more than 3? Coupling: 0/1/2
- What share of signups is organic, and is the app on a custom domain? Coupling: 0/1, or unscored
Pass condition: all five answers have a named source, and the totals map to one cell. Stop condition: any criterion is unknown; use Not yet — instrument first until measured.
Each outcome starts with a different action
Stay means document and monitor
Record why and name the trigger that would change the answer. Staying is a complete decision.
Instrument first means inventory before quoting
Begin with a read-only inventory: deployed edge functions versus functions in the repo, stored secret names rather than values, live-mode endpoint count, configured auth providers, and custom-domain presence. Do not extract secret values.
Before quoting, check whether the repository’s migrations still describe the linked database schema.
From a local project that has a supabase/config.toml, current migration files, Docker available, and an authorised link to the target project, use the remote flag documented by the Supabase CLI reference, checked 2026-08:
npx --yes supabase@2.115.0 link --project-ref <project-ref>
npx --yes supabase@2.115.0 db diff --linked
This is a schema comparison. The CLI applies local migrations to a shadow database and compares that schema with the linked project.
No generated SQL means the supported schema diff found no change. Generated SQL means the linked schema differs from the local migration result and needs review. Do not apply generated SQL to production as part of this check.
A clean result is not proof that the repository is a complete migration package. The CLI documentation lists known gaps for publication changes, storage buckets, and views using security_invoker.
The command also does not inventory database data, deployed function code, auth-provider settings, or secret values.
This check was reproduced in 2026-08 on a demo project: a Lovable project with a Supabase backend, one migration file, linked from the owner’s own Supabase account. Supabase CLI 2.115.0, Docker 29.1.3. The project reference is redacted below.
On that project the diff did not come back empty. Before anything was changed, it generated:
drop extension if exists "pg_net";
set check_function_bodies = off;
CREATE OR REPLACE FUNCTION public.rls_auto_enable()
RETURNS event_trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO 'pg_catalog'
AS $function$
-- enables row level security on newly created tables in public
$function$
;
grant delete on table "public"."coupons" to "anon";
grant insert on table "public"."coupons" to "anon";
-- 20 grant statements in total, across four tables
An event-trigger function, twenty table grants, and an extension present in the database but absent from the migration file.
To see the same command report a change made outside the migration flow, one column was added directly in the SQL editor:
alter table public.coupons add column demo_drift_marker text;
The next run reported it alongside the items above:
alter table "public"."coupons" add column "demo_drift_marker" text;
Dropping the column removed that line and left the rest in place. On this project there was no clean baseline: the first output above is already drift.
The same run printed the CLI’s own warning:
Found drop statements in schema diff. Please double check if these are expected:
drop extension if exists "pg_net"
That is the concrete reason not to apply generated SQL to production as part of this check: here it would have dropped a platform extension.
The documented gaps were visible too. A storage bucket created in the dashboard produced no change at all — the generated SQL before and after creating it was identical.
Two operational notes from the same session. The first invocation took 312 seconds, including the container image pull. That first invocation also failed with PGDELTA_SCRIPT_ERROR and unsupported or invalid secret format while the environment initialised; three later runs of the identical command succeeded. Re-run before treating a first failure as a result.
The inventory, criterion by criterion
Five read-only checks, one per scored criterion. The commands below run through npx, so no global install is required, and the version is pinned to the one this inventory was run with.
Deployed edge functions versus functions in the repo.
npx --yes supabase@2.115.0 functions list --project-ref <project-ref>
ls -1 supabase/functions/
On the demo project both sides were empty: the CLI returned its header row with no entries, and the repository had no supabase/functions/ directory. Any function the first command lists and the second does not is a function that has to be re-created by hand.
Stored secret names.
npx --yes supabase@2.115.0 secrets list --project-ref <project-ref>
The output columns are NAME and DIGEST. Values are not printed, so this list can be captured without extracting secrets. Each name is one configuration item to re-create.
Configured auth providers. Supabase dashboard, Authentication, Providers. The demo project had one enabled: Email. Each enabled provider is manual reconfiguration after a move.
Inbound webhook endpoints with side effects. Stripe dashboard, Developers, Webhooks, counted per mode. The scripted equivalent is the documented list endpoint, where each object carries a livemode field to count on:
curl -G https://api.stripe.com/v1/webhook_endpoints -u "<secret-key>:" -d limit=100
This is the one item in the list not reproduced for this article. The response shape is from the Stripe API reference, checked 2026-08.
Custom domain. Lovable project settings, Domains. The demo project had none and was not published, which leaves the acquisition criterion unscored — and an unscored criterion returns Not yet — instrument first whatever the other total says.
A partial move isolates the component carrying the stakes
When the stakes sit on the payment path, that component may be the webhook receiver. A stable receiver URL can let later hosting changes happen behind one integration boundary, provided the receiver does not itself depend on the component being moved.
A staged cutover separates rollback points
If the source is another Supabase project and you migrate its auth schema, Supabase documents that user rows and hashed passwords can move between projects. A differing JWT secret on the target project invalidates existing tokens, so users have to sign in again.
This project-to-project behavior was checked in the Supabase migration guidance in 2026-08.
Do not assume the same password path for Lovable Cloud. Lovable’s external deployment guidance, checked 2026-08, says its Cloud-to-Supabase export can move user data but cannot export user passwords. That path needs a password-reset plan.
Schedule whichever user-visible event applies to the project’s actual source backend. Give the data move its own rollback point because the repository does not contain database data.
Architecture and obligations change the answer
Re-score when the architecture or obligation changes, not on a calendar:
- the first live-mode charge;
- the first regulated data field;
- the fourth webhook endpoint with side effects;
- organic acquisition crossing 30% on a custom domain; or
- uptime or response-time language entering the contract.
One additional trigger is data volume. Lovable’s external deployment documentation, checked 2026-08, states that a Lovable Cloud database export is limited to 5 GB and can be requested once per 24 hours.
The relevant measure for that export path is export size, not signup count. Approaching the limit can constrain how many export-based rehearsals fit into a short window. Re-score before the limit becomes part of the cutover plan.
How we’d assess a project in the middle bands
For a project in a middle band, the assessment starts where your inventory stops. You can list what is deployed and stored yourself. We add classification and order: moves with the repo, must be re-created, or must be re-pointed with a cutover window.
The two judgment calls are whether migration files still match the database and which deployed items have no source-controlled counterpart. We then sequence identity, data, webhooks, and domain work against this project’s users and obligations. The output is a written dependency map and an order of operations.
Re-platforming an app is development work, not incident restoration — that is outside what this desk takes on. What we produce is the dependency map and the sequence; the build is yours or your client’s.
Obligations set the migration band
In this framework, the migration decision is based on what a Lovable app is on the hook for, not its user count alone.
This matrix tells you which band a project is in and what that band costs you. It does not tell you the migration will go well — the things that go wrong are the couplings nobody inventoried, and a framework can only score what you already know is there.
If you have a client project sitting in one of the middle bands and need the coupling inventory before you quote it, the Production Independence Assessment produces the dependency map and the ordering — scope and price agreed before anything in production changes.