Field Notes
How to fix a broken Lovable app (and when to get help)
Most broken Lovable apps are not broken in a deep way. They are in a loop: something stopped working, a prompt tried to fix it, the fix broke something else, and each round made the code harder to reason about. This guide is the order we work in when a founder sends us one, written so you can do most of it yourself. It covers what to do first, the problems we find most often and how to fix each, and a short security checklist before real users arrive.
Facts about Lovable here come from its own documentation as of October 2026. Lovable changes quickly, so check the current docs if a menu has moved.
First, stop the loop
Every prompt you send from a broken state starts from a broken state. Before changing anything else, go back to the last version that worked. Lovable's version history restores the code to any earlier change. Two things to know about it: it restores code only, so data already written to your database stays as it is; and it does not refund the credits spent getting there.
If you cannot tell which version was the last good one, restore the most recent version you are sure about and add the newer changes back one at a time.
Find the real error
"It's broken" gives the AI nothing to work with. The real error is almost always written down somewhere:
- The browser console. Press F12, open the Console tab, reproduce the problem, and copy the first red error, not the last.
- The Network tab. A request to Supabase that returns 401 or 403 is usually an access rule or sign-in problem; a 500 from an Edge Function means the function itself failed.
- The back end's logs. Edge Function logs and database logs show errors the browser never sees, such as a missing secret or a failed query.
Paste the exact message into Lovable. Better still, use chat mode or plan mode first, neither of which changes code, and ask it to explain the cause and propose a fix before it edits anything. Then make one change per prompt and check it before the next. The "Try to fix" button has a small number of free uses a day, after which it spends credits, so pressing it repeatedly is an expensive way to stay stuck.
Get the code out of Lovable
Connect the project to a GitHub repository you own. Lovable's GitHub sync works in both directions on one branch, usually main: changes pushed there appear in Lovable, and Lovable's changes appear there. Pushes to other branches do not sync, which is useful: a developer can work on a branch and merge only when the change is ready. If both sides change at the same time, Lovable pushes its version to a separate lovable-sync branch instead of overwriting.
With the code in GitHub you have a full history outside Lovable, and any developer can read the app without needing your Lovable login. On paid plans you can also export the project as a ZIP.
The problems we find most often
These are the causes behind most "broken" Lovable apps we see, roughly in order of what they cost when missed. The Lovable page has the short version of each.
1. Row-level security off, or allowing everyone
Your app's browser code talks to the database directly with a public key. Row-level security (RLS) is what stops that key reading every row of every table. Lovable's own security documentation says misconfigured RLS rules are a common cause of data leaks. Tables created with SQL do not get RLS switched on automatically, and a policy such as using (true) counts as a policy while protecting nothing.
If you can open the SQL editor for your database, this lists tables with RLS switched off:
select tablename
from pg_tables
where schemaname = 'public' and rowsecurity = false;
Our row-level security note has a second query that finds policies allowing every row, and explains the subtler failure: a policy that exists but checks the wrong thing. The only real proof is to sign in as one test user and try to read another user's data.
2. Secret keys in the browser
Lovable's secrets store keeps API keys out of the browser: secrets are injected only into Edge Functions and server-side code. Problems start when a key goes around it. Any variable starting VITE_ is built into the browser bundle, so anyone can read it. A Supabase service_role key there bypasses RLS completely, and a Stripe secret key or an AI API key there can be used by anyone with developer tools open. This often happens after a prompt like "fix the permission error", where the quickest way to make an error go away was to use a key that ignores permissions.
The fix: create new keys and revoke the old ones first, because a key that was ever public must be treated as public. Then move every call that needs a secret into an Edge Function that reads it from the secrets store.
3. Stripe payments that unlock on the wrong signal
Lovable's Stripe integration stores your secret key as a secret and calls Stripe from Edge Functions. Webhooks are optional, and if you want them you set them up in Stripe yourself. Without one, an app usually grants access when the browser reaches the success page, which is not proof of payment, and never removes access when a subscription is cancelled or a card fails.
Three details catch people out. Stripe's signature check needs the raw request body, so the function must read it as text before parsing it. A Supabase Edge Function requires a signed-in user's token by default, and Stripe sends none, so the webhook function needs that check turned off and Stripe's signature checked instead. And the Lovable preview has no test environment: with a live key, a payment in the preview is a real charge.
// supabase/functions/stripe-webhook/index.ts (the essentials)
const body = await req.text(); // raw body, before any JSON parsing
const event = await stripe.webhooks.constructEventAsync(
body,
req.headers.get("stripe-signature")!,
Deno.env.get("STRIPE_WEBHOOK_SECRET")!,
undefined,
Stripe.createSubtleCryptoProvider(),
);
# supabase/config.toml
[functions.stripe-webhook]
verify_jwt = false # Stripe sends no user token; the signature is the check
Store each subscription's state in your database from these events, decide access from that state, and test checkout, renewal, a failed payment and a cancellation before you switch to live keys.
4. Works in the preview, breaks once published
If the app uses your own Supabase project, adding a custom domain in Lovable does not add it to Supabase's authentication redirect settings; you have to, or sign-in links send people to the old address. On Lovable Cloud this is done for you once the domain is live. If you deploy the GitHub copy to another host, that host needs every build-time environment variable, and an older React and Vite project needs a rule that sends every route to index.html, or refreshing any page but the home page returns a 404. (Projects created since May 2026 use TanStack Start, which renders on the server and has different hosting needs.)
5. Sign-in loops
Users bouncing between the login page and the dashboard usually means a protected page decides before the session has been restored, or several pieces of code listen for sign-in changes and fight. Keep one place that owns the session, wait for it to load before deciding where to send the user, and handle the email confirmation and password reset links explicitly. Test in a fresh private window and on a phone.
6. Admin checks that exist only on the screen
Hiding the admin page from ordinary users does not stop them calling the same database queries from the browser console. Roles must live where users cannot change them, and be enforced by database policies and by server functions that check who is calling.
7. One database for everything
A prototype has one database, and you end up testing on your customers' data. Changes made by hand in a dashboard are not in GitHub, so nobody can rebuild the database or try a change safely first. Capture the schema as migrations in the repository, create a separate database for testing, and turn on backups. Remember that restoring a Lovable version does not roll back data, so backups are the only undo for a bad data change.
A security checklist before real users
Lovable runs a quick security scan when you publish, covering access rules, known-vulnerable npm packages and unprotected MCP servers, and offers a deeper scan you run yourself. By default the findings do not block publishing, so read them. Then go through this list:
- Every table has row-level security on, and no policy allows every row.
- Signed in as user A, you cannot read, change or delete user B's records, from the app or the browser console.
- Storage buckets holding private files are private, with policies like the tables.
- No secret key is in front-end code or a
VITE_variable; every key ever pasted into a prompt has been replaced. - Admin actions are enforced on the server, not just hidden in the interface.
- Payments: access comes from verified Stripe webhook events, and cancelling removes it.
- Sign-in redirect URLs list your real domain, and password reset works end to end.
- Production has its own database, separate from where you experiment, with backups on.
- The code is in a GitHub repository you own.
- Errors are reported somewhere you will see them, not just in a log nobody reads.
When to get help
Bring in an engineer when the same problem survives a restore and three careful prompts, when you are about to take payments or store personal data, or when a security finding is one you do not fully understand. None of these mean starting over. Under Lovable is a standard stack, and most of what it built is usually worth keeping.
That is the work we do. A free 20-minute call tells you what we expect to find. The Quick Security Check ($495, in 48 hours) checks your app for the problems above from the outside and tells you in writing what is exposed. How we fix Lovable apps, or see every price.