Websites · Glossary

What is row level security?

Row level security, or RLS, is a database feature that applies an access rule to every row of a table. Once enabled, a query returns or changes only the rows a policy allows for the current user, such as their own orders, published posts, or everything for an admin. In PostgreSQL and Supabase, a table with RLS and no policy denies everyone.

Row level security moves a rule like "you may only see your own bookings" out of the page and into the database. A page can be bypassed by anyone who edits a request in their browser; a policy cannot.

· Co-founder

6 min read · Published

Four common row level security setups
SetupWho can readWho can changeTypical table
No RLSAnyone the table is exposed toAnyone the table is exposed toNone that a browser can reach
Owner onlyThe person whose id is on the rowThe same person, checked on saveOrders, bookings, profiles
Public readEveryone, often only active rowsNobody except adminsMenus, services, published posts
AdminPeople listed in an admins tablePeople listed in an admins tableEvery managed table, beside other policies

What row level security is

A normal table permission is all or nothing: a role can read the table or it cannot. Row level security adds a second layer that runs for every row. The database evaluates a policy, a small true or false expression, against each row and the person asking, and only rows where it is true are visible or changeable. In PostgreSQL this is switched on per table, and from that moment the default is deny: with RLS enabled and no policies, ordinary users see nothing. Platforms such as Supabase rely on this because their browser apps talk to the database directly through an API with a public key. That public key identifies the project, not the person; the signed in user's token is what the policies check. Without RLS, anyone holding the public key could read every row of every exposed table, which is why it is the first thing to check on any such app.

How a policy works

A policy names a table, an action (select, insert, update or delete), the roles it applies to, and up to two expressions. USING decides which existing rows a person can see or touch; WITH CHECK decides whether a new or changed row is allowed to be saved. In plain words, a typical bookings policy reads: signed in people may select bookings where the booking's user id equals their own id, and may insert a booking only if the user id they are saving is their own. A public read policy on a services table might say anyone, signed in or not, may select rows where active is true. An admin policy says any signed in person who appears in an admins table may do anything. Policies for the same action combine with OR, so adding a policy can only widen access, never narrow it, which is worth remembering when one rule seems to be ignored.

When it matters

RLS matters whenever a browser or phone app reads the database directly rather than through a server you wrote. It matters most on tables that hold personal data, money or anything one customer should not see about another: orders, bookings, messages, invoices, files and profiles. Even with a server in between, policies are a safety net if a bug in that server forgets a filter. They also simplify code, because every query can ask for "bookings" and the database adds "that belong to this person" by itself. There are costs. Policies run on every row, so an expression that calls a slow function or joins a large table can slow queries; indexes on the columns used in policies help. And policies cannot hide individual columns, so data such as staff pay rates that only some people may see belongs in a separate table with its own policy.

Common mistakes

Forgetting to enable RLS on a new table, which on platforms with a public API can expose it to the whole internet. Writing a select policy but no WITH CHECK on insert or update, so a person can save a row claiming to belong to someone else. Allowing updates on a whole row when only one column should change, which lets a customer edit the price or the time on their own booking; changes like that belong in a function that edits only what it should. Using a policy of "true" for signed in users and assuming that means "my customers", when on a shared login it means anyone with an account. Checking access only through the app's own screens rather than by querying as each kind of person. And testing as the database owner or with a service key, both of which skip RLS and make every policy look like it works.

Where it shows up in OneCraft

When a site needs data, the AI gives it a database and every table it creates must have row level security. Before a generation step can finish, the AI runs an access check on each new table as an anonymous visitor and as a signed in person, and as an admin when the site has one, and fixes the policies if the results are wrong. Admin access is a separate site admins table checked by an is_admin function inside the policies, not a hidden menu. The capability guides also tell the AI that sign in is shared across sites in OneCraft's database pool, so a policy that allows every signed in person really means anyone with an account anywhere. The migrations and a schema.sql file with the policies are written into the site's code, so the rules can be read and reviewed.

Questions people ask

Is row level security enough to secure an app on its own?

It covers who can read and change rows, which is the biggest risk for apps that talk to the database directly. You still need storage rules for files, strong sign in, functions for actions that must change only certain columns, and care with secret keys. Think of it as the lock on each drawer, not the whole building.

Does row level security slow down queries?

A little, because the database evaluates the policy for each row. Simple comparisons such as user id equals the signed in id are cheap, especially with an index on that column. Policies that call functions doing extra queries or join large tables can be slow, so keep them simple and index the columns they use.

Can row level security hide some columns of a row?

No. A policy decides whether a whole row is visible. To hide a column, such as a reserve price or an hourly rate, move it into a separate table with its own stricter policy, or expose the data through a view or a function that returns only the allowed columns.

How do I test my row level security policies?

Query each table as each kind of person: a signed out visitor, an ordinary signed in user, a second user, and an admin. Check reads, inserts, updates and deletes for each. Never test with a service key or as the table owner, because both bypass the policies and every query will appear to succeed.

What is a security definer function in Postgres?

It is a function that runs with the rights of the person who created it rather than the caller. It lets an app do a narrow, checked action, such as booking a slot or placing a bid, without giving the user direct write access to the table. It must check its own inputs, because it can bypass the table's policies.

Make one with websites

The button opens the generator with this use case already described. Change the wording to match your own.

Create a website with OneCraft

Related questions

Sources

Written and checked by the OneCraft team. Last checked .