FixedLaunchBack

fix guide

Supabase data not saving, no error

· · updated

All fix guides

You fill in a form and press save. The app says it worked, or says nothing. You refresh and the data is gone. The Supabase table is empty and no error shows anywhere.

That silence is how Supabase behaves. When a security rule blocks a read, an update or a delete, the request doesn't fail. It returns nothing and carries on. Check these five in order. Most take a few minutes. Every RLS error shape, with the policy that fixes it, is in /guides/supabase-rls-errors.

1. Read the error your app throws away

Supabase's JavaScript client doesn't throw when a request fails. It hands back an object with data and error. If the code only reads data, a failed save looks like a successful one.

Find the save in your code. It looks like const { data, error } = await supabase.from("todos").insert(...). Add console.log(error) on the next line, save something on the live site and open the browser console. An error with code 42501 and the words "row-level security policy" means a rule blocked the insert. Step 2 fixes that.

Not sure where the save lives? Ask your builder (Lovable, Bolt, Cursor) to list every Supabase call and to show the user a message whenever one returns an error.

signs The save button works, the console shows nothing and the table stays empty. Or the console shows 42501 once you log the error.

2. Row Level Security with no matching policy

Row Level Security (RLS) decides which rows each user can read and change. When it's on and a table has no policy for an action, that action is blocked for everyone using your app's public key.

Blocked actions fail in different ways. An insert returns the 42501 error from step 1. A select, update or delete returns no error. It acts on zero rows, so you get an empty list or nothing changes.

In Supabase, open the Policies page for the table and check that each action your app uses has a policy. A simple one for a to-do app lets users insert and update rows where user_id equals their own id. An update also needs a select policy, so the database can find the row to change.

The Table Editor in the dashboard ignores these rules, so adding a row there proves nothing. Don't switch RLS off to make the problem go away. Anyone with your public key could then read and change the whole table.

signs Reads return an empty list although the table has rows. Inserts fail with 42501. Updates seem to run and change nothing.

3. The update or delete matches no rows

An update or delete with a filter, like .eq("id", 5), only touches rows that match. If none match, Supabase still counts it as a success and returns no error. A wrong id, or a row the user isn't allowed to see, gives the same quiet nothing.

Add .select() to the end of the call. The response then lists the rows that changed, and an empty list means none did. Inserts work the same way: without .select(), data comes back empty even when the save worked. A screen that waits for that data can look stuck.

signs The call finishes with no error and the table doesn't change. The response is an empty list or null.

4. The user isn't signed in

Most policies compare the row's user_id with auth.uid(), the id of the signed-in user. If nobody is signed in, auth.uid() is null, the comparison is never true and the rule blocks the request. The request runs as an anonymous visitor.

Also check that the app sends the user's id when it inserts. A row with an empty or different user_id fails a policy that wants the two to match.

Test this on the live site, not the preview. A session that works in the builder's preview can be missing on your own domain. If sign-in itself breaks only in production, fix that first.

signs Saving works right after you sign in and fails after a refresh or in another browser.

5. The app points at a different project

The live site needs the right Supabase URL and public key. If they are missing or belong to a second project, saves go to a project you aren't looking at. Open the Network tab in the browser, save something and read the address of the request. The part before .supabase.co is the project ref. Compare it with the project in your dashboard.

Vite apps (Lovable, Bolt) use VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY. Next.js apps use NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY. These values are baked in when the site builds, so redeploy after you change them.

signs The request goes to a project ref you don't recognize. Or the data is in one Supabase project and you are checking another.

Test it like a new user

  1. Open the live site in a private window and sign in.
  2. Save one new item and check that its row appears in the Supabase table.
  3. Edit that item and check the change in the table.
  4. Delete it and confirm the row is gone.
  5. If the data is private, sign in as a second user and check they can't see or change the first user's rows.

Common questions

Why does my Supabase save do nothing and show no error?

Your code probably ignores the error. Supabase's client returns an object with data and error instead of throwing, so log error after the call and save on the live site. Check the browser console. Code 42501 with the words row-level security policy means a rule blocked the insert.

Why do my Supabase reads return an empty list even though the table has rows?

Row Level Security is on and the table has no policy for that action. Blocked selects, updates and deletes return no error and act on zero rows, so you get an empty list. Open the table's Policies page and check each action your app uses has a policy. Don't turn RLS off.

Why does my update or delete change nothing and show no error?

The filter matched no rows, so Supabase counts the call as a success and changes nothing. Check the id in .eq() and that the user is allowed to see the row. Add .select() to the call, and an empty list in the response means no rows changed.

Still stuck?

Not sure this is the cause? Paste your app's link into the free scan. It lists what a visitor's browser can see, like leaked keys and exposed files, worst first. The score is free; every fix as a prompt for your builder is $9, one-time.

Want a person to look inside? See the diagnosis

More fix guides

See all fix guides