Supabase RLS with examples: how to prevent a user from seeing other users' data
Published on 2026-10-26 by frfrand
Practical RLS policies with auth.uid and permission tests

You have a notes table with user_id. If the API returns all notes and the frontend only filters its own, any curious user can request someone else's data. Row Level Security allows applying policies in PostgreSQL to restrict rows.
The idea is to solve a data or deployment problem that appears in real applications. We will see the concept without fluff, an implementation example, and what to check before taking it to production.
What Supabase RLS does in practice
Supabase uses PostgreSQL and its authentication integration can expose the user identifier via auth.uid() in the correct context. RLS requires being enabled on each exposed table and defining policies for SELECT, INSERT, UPDATE, and DELETE that correspond.
Simply repeating a recipe is not enough if you don't understand who reads the data, from what process, and with what permissions. In an application with reservations, invoices, or users, a race condition or a poorly made access policy can be more serious than a slow API.
A concrete example
ALTER TABLE public.notas ENABLE ROW LEVEL SECURITY;
CREATE POLICY "leer notas propias"
ON public.notas FOR SELECT TO authenticated
USING (user_id = (SELECT auth.uid()));
CREATE POLICY "crear notas propias"
ON public.notas FOR INSERT TO authenticated
WITH CHECK (user_id = (SELECT auth.uid()));
The names, tables, and keys in this example are illustrative. Try it first in a test base or environment and adapt permissions, indexes, and versions. When there are write operations, do not run sample queries on customer data without reviewing what they modify.
Step by step
- Add a proprietary
user_idcolumn of a type compatible with the Auth identifier. - Enable RLS on the exposed table and define a minimal SELECT policy.
- Create separate INSERT/UPDATE policies that check the correct owner.
- Test with two different users and one unauthenticated user, not just with admin.
- Do not expose the service role key in the browser: it can bypass RLS controls.
At the end, do not settle for "the SQL did not return an error." Verify the actual effect from the API and with concurrency or permission tests as appropriate. In databases, there are errors that only appear when two users do something at the same time.
Example work project
On a class platform, each student sees their reservations. If the frontend receives all records and filters by client, RLS could prevent unauthorized reads when the request is made with the appropriate role and JWT.
That scenario helps distinguish performance from consistency. Speeding up a query is useless if it allows creating two reservations for a single spot, nor does a huge database fix the backend allowing querying another client's data.
The typical error
The service_role role in Supabase often bypasses RLS; using it by mistake from the client invalidates the model. Policies depend on the schema and SQL permissions: test all operations with real roles.
It's also a good idea to have a recovery plan. Before touching schemas, users, or policies, save a verifiable copy or a reversible migration and check that the application can keep running during the change.
How would I solve it with ArduMaker?
Another option is a Django or FastAPI API and PostgreSQL on ArduMaker with authorization implemented in the backend. It is not automatically equivalent to Supabase Auth + RLS: you would have to design and test those rules yourself.
ArduMaker allows you to deploy services from Git and work with databases and processes within the VPS. The panel does not replace your application logic: transactions, permissions, and validations must be correctly designed in the code and the database.
Three checks before publishing
Integrity: if a request is repeated or fails halfway through, do the data end up in a valid state?
Security: can an authenticated user access only their own information, and does the API use a base identity with minimal permissions?
Recovery: do you know how to restore the data and revert the compatible code version if an update goes wrong?
If you can answer those three questions with tests, your backend is much better prepared than when you only checked if the endpoint returned 200.
Resources to learn more
To wrap up: choose the simplest solution that allows keeping data correct, secure, and recoverable. Performance comes after measuring.