Write your first row-level security policies
Row-level security moves access control into Postgres, so a user only ever sees rows they are allowed to read — enforced once, honored by REST, realtime, and SQL alike. This guide enables RLS on a table, writes a per-user policy, adds tenant isolation, and shows how Scribase lints policies for cost.
1.Enable RLS on the table
Row-level security is off by default. Once enabled, a table denies all access until a policy grants it, which is the safe starting point.
alter table todos enable row level security;2.Let users read and write their own rows
A policy uses the authenticated user id from the JWT to scope rows. This one lets a signed-in user see and change only the todos they own.
create policy "own todos"
on todos
for all
using (user_id = auth.uid())
with check (user_id = auth.uid());3.Add tenant isolation for a SaaS schema
For multi-tenant apps, scope by an org claim carried in the JWT so a member of one organization can never read another organization’s rows.
create policy "tenant isolation"
on projects
using (org_id = (auth.jwt() ->> 'org_id'));4.Keep the policy fast
The common failure is not a wrong policy but a correct one that scans under load. Scribase lints policies for cost so you catch an expensive predicate before it reaches production; index the columns your policies filter on.
create index on projects (org_id);Common questions
Does a policy apply to the REST API and realtime?
Yes. Because policies live in Postgres, the /rest data API, realtime subscriptions, and direct SQL all enforce the same rules — there is no second access layer to keep in sync.
How do I know a policy will stay fast?
Scribase lints policies for cost so you catch an expensive predicate early, and you index the columns your policies filter on so scoping does not turn into a scan.
Ready to try it?
Start a free project and follow this guide against a real backend, or move an existing project in with a dry run first.