Guide
RLS error saving a profile right after sign-up (Lovable, Bolt, Supabase)
Short answer: Your profiles table has row level security on, but no INSERT policy that lets the new user write their own row, so the insert is rejected. Add an INSERT policy with with check ( (select auth.uid()) = user_id ), and a SELECT policy for the same user, then save the profile after the user is signed in.
Why it happens
Row level security (RLS) gives you authorization rules that run inside the database. A policy is attached to a table and runs every time the table is accessed. Think of it as adding a WHERE clause to every query.
For an insert, the rule that matters is the with check clause. It makes sure any new row fits the policy, so a user cannot create a row that belongs to someone else. An INSERT policy uses with check by itself and needs no using clause.
Supabase maps a signed-in request to the authenticated role and a signed-out request to the anon role. Once RLS is enabled, no data is accessible through the API with a publishable key until you create policies. If profiles has no INSERT policy for the role making the request, the new row is rejected and you see the "new row violates row-level security policy" error.
Two more things can cause trouble on a profile save:
- No SELECT policy. To perform an UPDATE, a corresponding SELECT policy is required. Without a SELECT policy, the UPDATE will not work as expected.
- A SELECT policy that does not cover the row. Supabase Storage uploads show this pattern. The Storage API runs an INSERT followed by
RETURNING *, and if no SELECT policy covers the new row, the database cannot return it and the whole transaction fails, even when the INSERT policy is correct. If your INSERT policy looks right but the error stays, check that a SELECT policy exists for the same user.
Also check that the user is actually signed in when the save runs. auth.uid() returns null when a request is made without an authenticated user, and null = user_id is always false in SQL. A policy written as auth.uid() = user_id then silently fails.
How to fix it
- In Lovable, open the RLS policies view at More → Cloud → Database → RLS policies. It lists each policy with its name, command (SELECT, INSERT, UPDATE, DELETE), the roles it applies to, and the rule expression. The view is read-only.
- Check that
profileshas an INSERT policy for signed-in users. If it does not, add one. In the SQL editor at More → Cloud → SQL editor, run:create policy "Users can create their own profile." on profiles for insert to authenticated with check ( (select auth.uid()) = user_id );
- Add a SELECT policy so the user can read their own row back:
create policy "Users can view their own profile." on profiles for select to authenticated using ( (select auth.uid()) = user_id );
- If your app updates the profile after creating it, add an UPDATE policy with both clauses:
create policy "Users can update their own profile." on profiles for update to authenticated using ( (select auth.uid()) = user_id ) with check ( (select auth.uid()) = user_id );
- Make sure the profile row's
user_idcolumn holds the signed-in user's ID. The policy comparesauth.uid()touser_id, so a row written with a different value is rejected. If your table links to the user throughidinstead (as in the Supabase docs'public.profilesexample), use that column in the policies. - If you want the profile created automatically at sign-up, use a trigger on
auth.users. The Supabase docs show ahandle_new_user()function markedsecurity definer set search_path = ''that inserts intopublic.profilesusingnew.id. The docs warn that if the trigger fails, it could block signups, so test it thoroughly. - If you prefer to change the policy by asking, tell Lovable in the project chat, for example: "Only allow users to create and see their own profile." Lovable sets up these rules automatically when it builds features that store user data.
- If you use your own Supabase project instead of Lovable Cloud, run the same statements in the Supabase SQL editor. The Supabase docs give the full sequence: enable RLS, revoke existing grants from
anonandauthenticated, then grant back only what the role needs.
How to check it worked on your live app
- Open a private window so you start with no saved session.
- Sign up with a fresh email address you have not used before.
- Complete the failing action: save the profile right after sign-up.
- Confirm the profile row is saved and shows your details. Then sign out, sign in again, and confirm you still see your own profile.
Prompt to paste into your AI builder
Paste this into your AI builder: "When a new user signs up and saves their profile, the save fails with a row-level security error on the profiles table. Add an INSERT policy on profiles for the authenticated role with check ( (select auth.uid()) = user_id ), a SELECT policy for the authenticated role using ( (select auth.uid()) = user_id ), and an UPDATE policy for the authenticated role with both using and with check set to ( (select auth.uid()) = user_id ). Make sure the profile row is written with user_id set to the signed-in user's ID, and that the save only runs after the user is signed in. Show me the SQL and the code change first, before applying anything."
FAQ
Why does the error only appear right after sign-up? The INSERT policy decides which new rows a role may create. If the policy is missing, does not apply to the role making the request, or its with check does not match the row, the insert is rejected. Also confirm the user is signed in when the save runs, because auth.uid() is null otherwise.
Do I need a SELECT policy as well as an INSERT policy? For updates, yes: an UPDATE needs a corresponding SELECT policy. A missing SELECT policy can also make a write fail when the database has to return the new row, as the Storage upload case shows.
What does auth.uid() return when nobody is signed in? It returns null. A policy like auth.uid() = user_id is then always false, because null = user_id is always false in SQL. The Supabase docs recommend writing auth.uid() IS NOT NULL AND auth.uid() = user_id to make the intent clear.
Should I name the role in the policy? Yes. Use the to clause, for example to authenticated. This stops the policy from running for anon users, since execution stops at the to authenticated step.
Can I review the policies without writing SQL? In Lovable, go to More → Cloud → Database → RLS policies. The view is read-only, so to change a policy you ask in the project chat.
Related guides
- Data won't save: 'new row violates row-level security policy' covers the same error on other tables and how to write the matching policy.
- Supabase "permission denied for table" (42501): the fix explains the grant error that appears before any policy runs.
- Users can see other users' data in my Lovable app: find it and lock it down helps when the profile policies are too open instead of too tight.
Sources
- Row Level Security | Supabase Docs
- User Management | Supabase Docs
- Database - Lovable Documentation
- Storage error: 403 Forbidden: 'new row violates row-level security policy' on upload
- RLS Simplified
VibeFix's check can show this on your live app: its AI agent signs up to your app like a new person, uses it on a phone and a computer, and shows what breaks. VibeFix checks; it does not fix your app.
Last checked: 18 October 2026, against the sources listed above.
VibeFix is independent. Lovable, Bolt, Replit, Base44, v0 and Supabase are trademarks of their owners.
Still stuck?
Paste your app’s link for a free sign-up check, no account needed. An AI agent signs up to your live app as a brand-new person, on a phone and a computer, and tells you whether a new person can finish.