Row-Level Security in Power BI: The Complete Guide
Row-level security looks simple until it isn’t enforced for the person testing it. This guide covers static and dynamic RLS with real DAX, how to test it without fooling yourself, the Write-permission bypass almost nobody mentions, and how RLS behaves differently once you’re on Direct Lake and OneLake security in Microsoft Fabric.
Row-level security (RLS) is a set of DAX filter rules attached to a semantic model that restrict which rows a given user can see, without duplicating the report or the model. RLS comes in two forms: static, where a role hardcodes a value like a single region, and dynamic, where a role uses USERPRINCIPALNAME() against a security table so one role automatically filters differently per user. RLS is defined in Manage Roles, published with the model, and enforced for report viewers โ but not for anyone with Write permission on the model, including most workspace Admins, Members, and Contributors by default.
What RLS Actually Does โ and What It Doesn’t
Row-level security filters rows, not visuals, pages, or files. A user with RLS applied still sees the same report, the same pages, and the same visuals as everyone else โ they just see fewer rows of data underneath them. That distinction matters because it’s the source of most RLS confusion: people expect RLS to hide entire report pages, and it doesn’t. If you need to hide a page for certain users, that’s a separate technique (dynamic page visibility, or splitting the report into separate apps), not RLS.
RLS is defined once, in Manage Roles, as a DAX filter expression attached to a table. When a restricted user opens the report, Power BI silently adds that filter to every query the model runs on their behalf. They never see the filter, never see a warning, and never see the rows that got excluded โ which is exactly the point, but also why untested RLS is dangerous in both directions: it can either expose data that should have been hidden, or hide data a legitimate user actually needed.
Where RLS Sits in the Security Stack
RLS is one layer among several: workspace roles control who can open the report at all, RLS controls which rows they see, OLS controls which tables and columns they can see, and โ if you’re on Microsoft Fabric โ OneLake security can apply row, column, and object security at the storage layer itself, before RLS even runs. Section 06 breaks down what each layer actually does.
Static vs Dynamic RLS โ With Real DAX
Both approaches use the exact same mechanism โ a DAX filter expression on a table in Manage Roles. The difference is whether the value is hardcoded or looked up per user.
Static RLS
A static role hardcodes one value. It’s simple, but it requires a separate role for every value you want to filter on, and you have to manually assign users to the correct role.
[Region] = "East"
This works fine for a handful of fixed roles โ for example, three regional manager roles that rarely change. It falls apart fast once you have dozens or hundreds of distinct filter values, because every new value means a new role and a manual re-publish.
Dynamic RLS
A dynamic role uses USERPRINCIPALNAME() (the user’s email/UPN) or USERNAME() combined with a security mapping table, so one single role filters differently for every user automatically. This is the pattern almost every production deployment actually uses.
[Region] = LOOKUPVALUE(
SecurityMapping[Region],
SecurityMapping[UserEmail], USERPRINCIPALNAME()
)With this pattern, you maintain one small mapping table โ ideally synced from Azure AD, an HR system, or a source of truth outside the report โ and every user who opens the report gets automatically filtered to their row(s) in that table. Adding a new user’s access is a data change, not a model change.
USERPRINCIPALNAME() vs USERNAME()
USERPRINCIPALNAME() returns the full UPN (usually the email address) and is the recommended function for cloud deployments. USERNAME() returns DOMAIN\username in on-premises/gateway scenarios and the UPN in the Power BI service โ the inconsistency between environments is a common source of dynamic RLS silently returning zero rows after a report is published from Desktop to the service with a gateway involved. Test in the actual target environment, not just Desktop.
Building and Testing RLS Step by Step
Modeling tab โ Manage Roles โ New. Name the role clearly (e.g., “Regional Manager โ Dynamic”), select the table to filter, and enter the DAX expression in the filter box. You can toggle between the default dropdown builder and a raw DAX editor for more complex expressions.
Modeling tab โ View As. Select the role, and for dynamic RLS, check “Other user” and enter a real user’s UPN to simulate their exact filtered view. Testing the role alone (without impersonating a specific user) only proves the DAX syntax is valid โ it doesn’t prove the right person sees the right rows.
After publishing, go to the semantic model’s Security settings in the Power BI service. Roles defined in Desktop appear here โ add the actual Azure AD users or security groups to each role. Static roles need explicit membership; dynamic roles typically get one broad group added once, since the filtering itself is handled by the mapping table.
From the semantic model’s Security page, use “Test as role” to open the report exactly as a member of that role would see it. Check totals against card visuals, confirm slicers only show authorized values, drill through to detail pages, and export to Excel to confirm the filter survives export โ a step people frequently skip.
Testing RLS while logged in as yourself, if you’re the report author or a workspace Admin/Member/Contributor, will always show unfiltered data โ not because RLS failed, but because it was never being enforced for your account in the first place. This single misunderstanding is behind more “RLS isn’t working” support tickets than any actual DAX bug. Section 04 covers exactly why.
The Permission Bypass Nobody Warns You About
This is the detail most RLS tutorials skip entirely, and it’s the one that causes real incidents: data-access rules, including RLS, are not enforced for any user who has Write permission on the semantic model. This isn’t a bug โ it’s documented, intentional behavior โ but it’s easy to miss until it causes a problem.
| Workspace Role | Has Write Permission? | RLS Enforced? |
|---|---|---|
| Admin | Yes, implicitly | No โ sees unfiltered data |
| Member | Yes, implicitly | No โ sees unfiltered data |
| Contributor | Yes, implicitly | No โ sees unfiltered data |
| Viewer | No, by default | Yes โ RLS applies normally |
Why This Matters in Practice
If a report gets distributed through a Power BI App and every recipient only ever has Viewer-equivalent access, this is invisible and harmless. The risk shows up when the same workspace is used for both authoring and distribution โ a common shortcut on smaller teams โ because everyone with edit access to build the report also has full, unfiltered access to every row, regardless of any RLS role they’re assigned to. If a “restricted” business user is ever added to a workspace as a Contributor instead of distributed through an App, their RLS role becomes decorative.
The fix is structural, not a DAX fix: separate authoring workspaces from distribution. Build and maintain the model in a workspace where only report developers have Contributor-or-higher access, then distribute to end users through a Power BI App, a separate audience-specific workspace where they only hold Viewer access, or row-level-secured embedded scenarios โ never by adding restricted business users directly into the authoring workspace.
RLS in Microsoft Fabric โ Direct Lake and OneLake Security
Everything above applies to Import and DirectQuery models the same way it always has. Direct Lake changes the mechanics, and Fabric adds an entirely separate security layer โ OneLake security โ that most Power BI-only content doesn’t cover at all.
RLS on Direct Lake
RLS roles on a Direct Lake semantic model are defined the same way, with the same DAX filter expressions, in the same Manage Roles interface. What changes is evaluation:
- Direct Lake on OneLake (the recommended configuration): the RLS filter is evaluated natively against the Parquet files in OneLake. Simple filters get pushed down efficiently.
- Complex DAX filters fall back automatically: if your RLS expression uses functions Direct Lake can’t evaluate natively โ LOOKUPVALUE, PATHCONTAINS, and iterator functions are the common culprits โ the engine silently falls back to DirectQuery against the SQL analytics endpoint for that query, which changes performance but not correctness.
- Direct Lake on the SQL analytics endpoint (instead of OneLake): applying RLS here forces a full fallback to DirectQuery for the model. If you’re configuring RLS on a Fabric semantic model, use Direct Lake on OneLake specifically, or you lose the performance benefit Direct Lake exists for.
Two Direct Lake Limitations Tied to Security
Bidirectional relationships aren’t supported in a Direct Lake model if the source relies on OneLake security RLS. And Direct Lake semantic models that depend on OneLake security don’t support backup operations. Both are easy to discover only after you’ve already built the model โ check them before you commit to an architecture, not after.
OneLake Security โ A Separate, Earlier Layer
OneLake security lets you define row-level, column-level, and object-level security once, at the OneLake data layer, so the rules apply consistently across every engine that reads the data โ Power BI, SQL, Spark, and APIs โ rather than redefining security separately in each consuming semantic model. It only applies to Direct Lake semantic models; Import models that copy data into memory aren’t covered by it at all.
Deny-by-default: users have zero access until explicitly granted a role Most permissive role wins: if a user is in a broad-access role AND a restrictive RLS role on the same table, the broad role wins and the RLS restriction is silently NOT enforced Shortcuts: security defined on the source folder applies even when accessed through a shortcut in a different Lakehouse
The “Most Permissive Role Wins” Trap
Assigning the same user to both a broad read role and a narrow RLS-restricted role on the same table doesn’t combine the restrictions โ it silently drops them. Keep restrictive and permissive OneLake security roles mutually exclusive per user, and treat “which roles is this person in, across every table” as something you audit deliberately, not something you assume from looking at one role at a time.
RLS vs OLS vs CLS vs OneLake Security โ Who Controls What
Row-Level Security (RLS)
Filters which rows of a table a user sees. Defined in the semantic model with DAX. Available for Import, DirectQuery, and Direct Lake.
Object-Level Security (OLS)
Hides entire tables or columns from a role, so restricted users can’t see them exist at all โ including in field lists and Q&A. Defined in the semantic model, typically with Tabular Editor.
Column-Level Security (CLS)
A narrower form of OLS that hides specific columns rather than whole tables โ for example, hiding a salary column while leaving the rest of an Employees table visible.
OneLake Security (Fabric only)
Row, column, and object security defined once at the OneLake storage layer, enforced across every engine that reads it. Applies only to Direct Lake semantic models, not Import.
How to Decide Which Layer to Use
If your report consumers never have direct query access to the underlying Lakehouse or Warehouse โ only to the published report โ semantic model RLS and OLS are usually sufficient and simpler to manage. If the same data is queried through multiple engines (Power BI, SQL endpoints, notebooks, external tools), enforce security once in OneLake so you’re not maintaining the same rules in five different places that can silently drift out of sync.
Common Mistakes and How to Catch Them
FAQs – Power BI Row-Level Security
Official Resources and Related Guides
Official Microsoft Resources
Related UIG Data Lab Guides
โ ๏ธ Accuracy Disclaimer
RLS, Direct Lake, and OneLake security behavior described here are sourced from official Microsoft Learn documentation as of July 2026, including Row-Level Security with Power BI, Integrate Direct Lake Security, and the OneLake security access control model. Microsoft Fabric features change frequently โ verify current behavior for your capacity SKU and region before relying on any specific security configuration in production. UIG Data Lab is an independent publication, not affiliated with or endorsed by Microsoft Corporation.