What Is Row Level Security
If you’ve built an app where different users should see different slices of the same data, you’ve run into a common problem: permissions aren’t always about “can they access the table?” Sometimes the table is shared, but each person should only see the rows meant for them.
That’s where row level security (often shortened to RLS) comes in. It’s a database access control that lets you decide, row by row, which data a user can read or change. The rules can depend on the user’s identity, their role, or what’s in their current session.
What row-level security means in simple words
Think of a table like a big spreadsheet. Without row-level security, you might grant a user permission to read the table, and they can see every row.
With row-level security, you add a check in front of each row. Even if the user can access the table, the database still verifies whether that row should be visible or editable for that user.
So what is row level security in plain English?
- Row-level security controls database access by row.
- A user can only access the rows they’re authorized for.
- Which rows are allowed can depend on who the user is, their role, their session, or other user-related info.
One key detail: RLS isn’t the same as hiding a column or masking a value. It limits access to *records* (rows), not fields (columns).
How RLS decides which rows a user can read or change
At a high level, RLS works like this:
- You define rules that say, “This user can see or change these rows.”
- When a query runs, the database checks those rules.
- It returns only rows that pass the checks.
Those checks use an “authorization context,” which can include:
- Identity: the specific user making the request.
- Role: for example “manager,” “support agent,” or “auditor.”
- Session info: values tied to the current logged-in session.
- Other user characteristics: whatever your system exposes for the current user.
Read vs change (depending on your setup)
Row-level security isn’t only about reading. It can also control writes like updates and deletes. In other words:
- You can create policies that limit which rows a user can read.
- You can create policies that limit which rows a user can update or delete.
That’s why people often describe RLS as controlling which rows a user can read or change.
Also, don’t assume it’s automatically protected everywhere. Some tables have no row security rules by default, so the database won’t automatically filter rows unless you set it up.
A simple row-level security example
Let’s keep one scenario so the idea stays clear across systems.
Scenario: users should only see their own orders
Imagine you have a table called `orders`:
- `order_id`
- `customer_id`
- `total_amount`
You want:
- User A to see only rows where `customer_id = A`
- User B to see only rows where `customer_id = B`
With RLS, the “gate” becomes: only return rows where the row’s customer matches the logged-in user (or the customer id(s) they’re allowed to access).
Conceptually, your rule looks like:
- “A user can select a row if `orders.customer_id` matches the user’s id (or matches what they’re allowed to access).”
Then you run a normal query like “show me my orders.” The query can target the same `orders` table for everyone. The difference is that the database filters out rows that don’t match the rule for that user.
Why this matters
Without RLS, you’d need to add logic into every query:
- `WHERE customer_id = current_user_id`
That can work, but it’s easy to get wrong over time. With RLS, the protection lives closer to the data. The database enforces it as part of authorization.
Row-level security versus column-level security
People mix these up because both are security controls, but they target different things.
- Row-level security controls which rows (records) a user can access.
- Column-level security controls which columns (fields) a user can access.
So the “unit of protection” is different:
- Rows determine *which records you get*.
- Columns determine *which fields you’re allowed to view*.
If you only need to hide certain fields (like salaries or internal notes), you’re dealing with column-level security. If each user should see only their own records, that’s row-level security.
How grants, policies, and filters work together
RLS usually isn’t one single toggle. It’s a mix of a few pieces:
**Grants**: permission to use the object
A grant gives a user permission to access something at all (like using a table or running certain operations). If the user doesn’t have the right base permission, RLS rules can’t help.
**Policies**: the actual row rules
A policy is where you define the row-level logic, like:
- “Allow users to read rows where the customer matches…”
- “Allow updates only for rows they own…”
This policy is the gatekeeper logic.
**Row filters**: how the policy turns into results
When you run a query, the system applies the policy like a row filter—it removes rows the user isn’t allowed to see or change.
Authorization rules inside the database (the core idea)
A big part of how RLS works is that authorization runs inside the database. That means the combination of:
- grants (who can access the table or operation)
- policies (which rows are allowed)
together decide what the user can actually read or change.
“Some tables have no policies by default”
This is another thing that trips people up: some tables don’t have row security policies set up by default. If no policy exists for a table, the database may not apply row filtering the way you expect. So you still need to plan which tables get RLS rules and which ones don’t.
How to use row-level security in SQL databases
Since SQL databases differ by vendor, there isn’t one universal “copy-paste syntax.” But the workflow is similar across most systems that support RLS.
Here’s the practical pattern:
- Decide what defines access
- Is it user id?
- Role?
- Organization id?
- Something from the session?
- Write the rule for allowed rows
- Read policy (select)
- Write policy (update/delete), if needed
- Make sure the user has the right base permissions
- Grants determine whether they can even try to access the table.
- Enable/apply the row-level protection for the table(s)
- Some systems require turning RLS on per table.
- Test with real queries
- Run the same query as different users and confirm they only see what they should.
A common mental model
If you’re building a multi-tenant app, the rule often looks like:
- “Tenant A can only see rows where `tenant_id = tenant A`.”
You don’t want every developer to remember to keep adding that `WHERE` clause forever. RLS makes it part of authorization, not something you rely on in each query.
RLS in PostgreSQL, Supabase, and Power BI
Different platforms use the same idea, but they implement it with different “hooks.”
What changes in PostgreSQL (and Supabase)
Row-level security in PostgreSQL is built into the PostgreSQL ecosystem, and Supabase uses it too. In both cases, the idea is the same:
- policies decide which rows a user can access
- authorization runs in the database
- grants and policies work together to control access
So if two users query the same table, they can get different results because the database applies the RLS rules.
What can differ is how you:
- define the policies,
- represent the logged-in user identity to the database,
- and enable row-level protection for each table.
If you’re building in Supabase, you’ll also map the “current user” concept to what Supabase exposes to the database for policy checks.
What changes in Power BI
In Power BI, the model isn’t “the database enforces RLS during SQL queries” in the same way it is with PostgreSQL.
Instead, RLS restricts data access for specific users of a semantic model through row-level filters. Those filters are applied so different users see different rows in their reports.
So the same business goal—“users only see their own data”—is achieved by:
- setting rules/filters tied to users inside Power BI’s model layer,
rather than relying only on database-side row authorization.
That’s why Power BI RLS is often described as row-level filters for users of a semantic model.
One shared idea, different mechanics
Here’s the common thread across all of them:
- You’re trying to answer: “Which rows should this user see or edit?”
- The difference is where the decision happens:
- SQL/PostgreSQL/Supabase: authorization rules live in the database layer.
- Power BI: row-level filters live in the semantic model/report layer.
Should row-level security be enabled?
RLS is powerful, but it’s not something you turn on blindly.
Enable row-level security when you truly need different users (or roles) to see different rows from the same table. It’s especially useful when:
- you have shared tables across users/tenants,
- forgetting a `WHERE` clause would expose data,
- you want the database to enforce row rules consistently.
But keep these cautions in mind:
- RLS shouldn’t replace permission thinking. Grants and policies work together. If you don’t grant the right permissions, users still can’t do anything.
- Policies may not exist by default. Some tables have no row security policies by default, so you need to set them up where you want filtering.
- Platform behavior differs. Power BI uses row-level filters on semantic models, while PostgreSQL/Supabase apply rules inside the database. Don’t assume the same configuration steps or expectations across tools.
If you’re building a system and you’re unsure whether you need it, start with a simple question: “Could a user accidentally see data that isn’t theirs if they have access to the table?” If the answer is yes, RLS (or an equivalent approach) is likely worth considering.
Before you configure anything, pick the platform you’re using and read the platform-specific RLS documentation for that system (SQL database vendor docs, PostgreSQL/Supabase docs, or Power BI semantic model RLS guidance). The core idea is the same, but the setup details and where the rules run can be very different.