Row Level Security (RLS) decides which rows each request may read or change. Every policy you write applies to the REST API, Realtime and Storage.
#Enable RLS
alter table public.todos enable row level security;With RLS enabled and no policies, the API returns no rows to anon and authenticated and rejects their writes. Add policies for the access you want.
#Roles
| Role | Used when |
|---|---|
anon | A request uses the anon key without a signed-in user |
authenticated | A request carries a signed-in user's access token |
service_role | A request uses the service_role key. It bypasses Row Level Security, so keep it on the server. |
#Policy examples
-- Anyone can read published posts
create policy "Public posts are readable" on public.posts
for select to anon, authenticated using (published);
-- Users can only change their own rows
create policy "Owners can update" on public.posts
for update to authenticated
using (author_id = auth.uid())
with check (author_id = auth.uid());
-- Require multi-factor authentication for a sensitive table
create policy "MFA required" on public.payouts
as restrictive for all to authenticated
using ((auth.jwt() ->> 'aal') = 'aal2');auth.uid() returns the id of the signed-in user, and auth.jwt() returns all of their token claims.
#A query that respects the rules
The policy is enforced by the database, so the client code does not need to filter by user:
create policy "Users manage their own todos" on public.todos
for all to authenticated
using (user_id = auth.uid())
with check (user_id = auth.uid());// Returns only the signed-in user's todos
const { data } = await stitchdb.from('todos').select('*')#Write policies in Studio
Database → Policies (and Storage → Policies for files) lists the policies of every table and lets you create, edit and delete them. Write policy with AI drafts a policy from a sentence for you to review.
In the policy editor, Build without SQL writes the rule from conditions you pick: a column, a condition (equals, is empty, contains, is an organization the user belongs to) and what to compare it with (the signed-in user's id or e-mail, a claim, or a value). The SQL it writes stays editable. Templates cover the usual cases: owner only, public read, signed-in users read, and, with Organizations turned on, organization members read and organization admins write. The organization templates expect an org_id uuid column.
#Check access
When a user can't see a row they should, or can see one they shouldn't, open Database → Policies → Check access. Pick a signed-in user, an anonymous visitor or custom token claims, then a table, a row, and whether they want to see, change, delete or insert it. Studio runs every policy as that user, with their token, and says in plain words which policy lets them in or keeps them out, for example: Fails: (owner = auth.uid()) is false. Here auth.uid() is 7c1…, owner is 41a….
The answer covers the things that are easy to miss: a missing grant (Postgres refuses before any policy runs), Row Level Security being off, a restrictive policy that has to pass as well, a policy that ends in an error, and an update or delete that is allowed for a row the user can't select (so where id = … finds nothing). Each check is compared with what Postgres really returns for that user. Nothing is changed: the check runs in a transaction that is always rolled back.
#Security alerts in Studio
Studio checks every table and view the API can reach (the public schema) and warns you before data leaks:
| Alert | What it means | What to do |
|---|---|---|
| RLS off (red badge, open lock) | RLS is disabled on the table. Anyone with your project's anon key can read and change all rows. | Click Enable RLS in the Table Editor or Advisors, then add policies. |
| RLS on without policies (green badge with a hint) | The table is protected, but the API returns no rows to anon and authenticated users and blocks their writes. | Click Add policy and allow the access you need. |
| bypasses RLS (amber badge on a view) | Views run with their owner's permissions unless security_invoker is on, so the policies of the tables they read are skipped. Materialized views have no RLS at all. | alter view public.my_view set (security_invoker = on); |
| N tables are public without RLS | A strip at the top of Studio pages, a card on the project overview and a count on Advisors in the Studio menu. | Click Review. Dismissing the strip hides it until the set of affected tables changes. |
Enabling RLS from an alert asks for confirmation, because the table returns no rows until policies exist. Advisors lists every finding with the affected objects and the SQL that fixes it.
-- Enabling RLS with no policies hides every row from the API
alter table public.todos enable row level security;
-- Fix a view that bypasses RLS
alter view public.todo_stats set (security_invoker = on);
-- Pin the search_path of a function
alter function public.count_open_todos() set search_path = '';