What is Row Level Security (RLS) in PostgreSQL, and how is it different from GRANT?
Suggested answer
PostgreSQL has two layers of permission control on a table, and a good answer tells them apart.
GRANT decides which operations a role may perform on the table: select, insert, update, delete. It doesn't distinguish rows: once you grant select, the role can read every row.
Row Level Security decides which rows that role may touch. After you run alter table ... enable row level security, every operation has to satisfy a policy, an SQL condition checked on each row. For a blog, for example:
create policy "Anyone can read published posts" on posts for select
to anon using (status = 'published' and published_at <= now());A few points worth making to show that you have used it for real:
- Turning on RLS with no policy denies everything by default. A
selectdoesn't raise an error, it returns 0 rows, so the page is empty even though the table has data. This is a very common mistake. - Writes are blocked silently: an
updateordeleteon rows you can't see affects 0 rows, while aninsertthat violates a policy raises an error. - The table owner, superusers and roles with BYPASSRLS skip RLS. So you must test policies as the application's own role (for example
set role anon), and the admin key must never live in the browser. - Why you need RLS when you already filter in code: with platforms that let the browser call the database directly through an API (like Supabase), the key sits on the client side and anyone can call it. A filter in your code protects nothing; a policy in the database is what blocks.
Bonus points: say that you should use both layers and grant only the operations that are needed, so that one wrongly granted permission doesn't expose or destroy data.