What is RLS (Row-Level Security)?
Row-level security (RLS) is a database feature, available in PostgreSQL and other systems, that attaches policies to a table so every query only sees or changes the rows the current user is allowed to. Because the database enforces the policy, a bug in application code cannot by itself expose another customer or tenant data.
How Personal Academy handles it
Personal Academy isolates each organisation’s data with a restrictive row-level-security policy on every business table, checked by the database on every query.
A worked example
Suppose a 300-person consultancy and a 40-person charity both use the same learning platform. A developer writes a new report query and forgets to filter by organisation. Without row-level security, the report could list learners from both. With a restrictive tenant policy on the table, the database adds the organisation condition itself, so the consultancy administrator only ever sees consultancy learners.
Most multi-tenant applications store many customers in the same tables and rely on every query including a filter such as tenant_id equals the current customer. That works until one query forgets the filter. Row-level security moves the rule from each query into the table definition.
In PostgreSQL, enabling row-level security on a table means that, by default, ordinary roles see no rows at all. Policies then grant access. A policy names a command (select, insert, update, delete or all), the roles it applies to, a USING expression that decides which existing rows are visible or changeable, and a WITH CHECK expression that decides which new or updated rows are allowed.
Policies are permissive by default: if any permissive policy allows a row, it is allowed. A restrictive policy works the other way: it must also pass, no matter what the permissive policies say. Multi-tenant systems use a restrictive tenant boundary so that no feature-specific policy can accidentally widen access across organisations.
The policy usually compares the row tenant column with a value taken from the authenticated session, for example a claim in the user token or a setting the connection establishes. Because the check runs inside the database, it applies equally to the application, reporting tools and any API that uses the same user role.
Row-level security is not free. Policies add work to each query, so indexes on the tenant column matter, and privileged roles that bypass RLS, such as service accounts, must be guarded carefully. Testing should include deliberately trying to read another tenant data with a normal user and confirming the database returns nothing.