Skip to main content
๐Ÿ›ก๏ธ Verified Technical Content: Written by Serhii Hrekov. | Last reviewed & updated in Git: July 21, 2026

How to write RLS Policies to ensure users can only edit their own profile data?

ยท 5 min read
Serhii Hrekov
Senior Software Engineer & System Architect specializing in Python, Web Systems, Cloud Infrastructure & Automation

In a traditional setup, you'd write a bunch of backend code to check if session.user.id === profile.id. In Supabase, you just tell the database the rules and let it act as the bouncer.

Row Level Security (RLS) is the engine that ensures your users can see a thousand profiles but can only click "Edit" on their own. Here is how to configure it correctly in 2026.

The "ID Match" Logicโ€‹

The secret sauce of Supabase RLS is a built-in function called auth.uid(). This function extracts the Unique ID of the user making the request directly from their JWT (JSON Web Token).

To secure a profiles table, we simply compare the ID of the row being touched to the ID of the user currently logged in.


The SQL Implementationโ€‹

You can run these commands in the Supabase SQL Editor.

1. Enable RLSโ€‹

By default, your table is either open to the public or locked completely. You must explicitly turn on the "Security Valve" first.

ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;

2. Allow "Read Only" for Everyoneโ€‹

Usually, you want users to be able to see each other's basic profile info (like a name or avatar).

CREATE POLICY "Profiles are viewable by everyone"
ON profiles FOR SELECT
USING (true); -- 'true' means no restriction on viewing

3. Restrict "Updates" to the Ownerโ€‹

This is the policy that prevents someone from opening the console and changing your username.

CREATE POLICY "Users can update their own profiles"
ON profiles FOR UPDATE
USING (auth.uid() = id); -- Only succeeds if the IDs match

Standard Policy Templatesโ€‹

Action (CMD)Policy Logic (USING / WITH CHECK)Result
SELECTtruePublicly viewable profiles.
INSERTauth.uid() = idUsers can only create their own record.
UPDATEauth.uid() = idOnly the owner can edit.
DELETEfalseNobody can delete (safer for profiles).

Critical: USING vs WITH CHECKโ€‹

When writing these policies, you'll see two different clauses. Here is the difference:

  • USING: Applies to rows already in the database. (e.g., "Can I see this row?" or "Can I find this row to update it?")
  • WITH CHECK: Applies to the new data you are trying to push. (e.g., "Is the new ID I'm trying to insert actually my ID?")

Pro Tip: For an UPDATE policy, it is often best to use both to prevent a user from accidentally changing their id to someone else's: ... USING (auth.uid() = id) WITH CHECK (auth.uid() = id);


Common Pitfallsโ€‹

1. The Service Role Bypassโ€‹

Remember that the service_role key (used in backend scripts or Edge Functions) bypasses RLS entirely. If your code isn't working as expected, double-check that your frontend is using the anon key.

2. Table Column Namesโ€‹

In this example, I assumed your primary key is named id. If your column is named user_id or owner, you must update the policy: USING (auth.uid() = owner_id);

3. Testing with "Impersonation"โ€‹

Supabase has a "User Impersonation" feature in the dashboard. Use it! You can select a specific user and browse the data to see exactly what they can (and cannot) see based on your new policies.


Sources & Technical Refsโ€‹

  • [1.1] Supabase Docs: Row Level Security - Official guide on the auth schema.
  • [2.1] PostgreSQL Docs: CREATE POLICY - Technical deep-dive into the Postgres policy engine.
  • [3.1] PostgREST: Lifting the Veil - How JWTs are translated into database roles.

Related articles