Business Professionals
Power BI | Power Pivot | Power Query | DAX
Cloud Flows | RPA | AI Builder | Copilot
60+ Formulas | Data Stories | Advanced Reporting & Modeling
VB Programming | Report Automation |
MS-Office Automation
Techno-Business Professionals
Power BI | Power Query | Advanced DAX | SQL - Query &
Programming
Microsoft Fabric | Power BI | Power Query | Advanced DAX |
SQL - Query & Programming
Power BI | Power Apps | Power Automate | Copilot Studio | Power Pages | Dataverse
Microsoft Power Apps | Microsoft Power Automate
Power BI | Adv. DAX | SQL (Query & Programming) |
VBA | Python | Web Scrapping | API Integration
Power BI | Power Apps | Power Automate |
SQL (Query & Programming)
Power BI | Adv. DAX | Power Apps | Power Automate |
SQL (Query & Programming) | VBA | Python | Web Scrapping | API Integration
Power Apps | Power Automate | SQL | VBA | Python |
Web Scraping | RPA | API Integration
Technology Professionals
Power BI | DAX | SQL | ETL with SSIS | SSAS | VBA | Python
Power BI | SQL | Azure Data Lake | Synapse Analytics |
Data Factory | Databricks | Power Apps | Power Automate |
Azure Analysis Services
Microsoft Fabric | Power BI | SQL | Lakehouse |
Data Factory (Pipelines) | Dataflows Gen2 | KQL | Delta Tables | Power Apps | Power Automate
Power BI | Power Apps | Power Automate | SQL | VBA | Python | API Integration
Power BI | Advanced DAX | Databricks | SQL | Lakehouse Architecture
Business Professionals
Power BI | Power Pivot | Power Query | DAX
Cloud Flows | RPA | AI Builder | Copilot
60+ Formulas | Data Stories | Advanced Reporting & Modeling
VB Programming | Report Automation |
MS-Office Automation
Techno-Business Professionals
Power BI | Power Query | Advanced DAX | SQL - Query &
Programming
Microsoft Fabric | Power BI | Power Query | Advanced DAX |
SQL - Query & Programming
Power BI | Power Apps | Power Automate | Copilot Studio | Power Pages | Dataverse
Microsoft Power Apps | Microsoft Power Automate
Power BI | Adv. DAX | SQL (Query & Programming) |
VBA | Web Scrapping | API Integration
Power BI | Power Apps | Power Automate |
SQL (Query & Programming)
Power BI | Adv. DAX | Power Apps | Power Automate |
SQL (Query & Programming) | VBA | Web Scrapping | API Integration
Power Apps | Power Automate | SQL | VBA |
Web Scraping | RPA | API Integration
Technology Professionals
Power BI | DAX | SQL | ETL with SSIS | SSAS | VBA
Power BI | SQL | Azure Data Lake | Synapse Analytics |
Data Factory | Azure Analysis Services
Microsoft Fabric | Power BI | SQL | Lakehouse |
Data Factory (Pipelines) | Dataflows Gen2 | KQL | Delta Tables
Power BI | Power Apps | Power Automate | SQL | VBA | API Integration
Power BI | Advanced DAX | Databricks | SQL | Lakehouse Architecture
You’re not judged by how pretty your Power BI report looks; you’re judged by who can see what. This guide walks through how to design robust row-level security (RLS) in Power BI, starting from simple role filters and ending with scalable, multi-tenant models that can support dozens of clients or business units without turning into a maintenance nightmare. If you want to go beyond ad-hoc setups and build production-grade models, pairing this with deeper practice on solid Power BI modelling and reporting is a smart move.
RLS is not just a feature toggle you flip on at the end of a project. Poor RLS design shows up as:
A robust design has:
The rest of this article builds that step by step.
You already know where RLS lives in Power BI Desktop (Modeling ➜ Manage roles). The parts that matter for robust design are:
USERPRINCIPALNAME() or USERNAME()).Once you think in these terms, you stop writing random filters in roles and start designing a security model.
For small scenarios, static roles are fine. Example: you have a Sales model and want:
Assume your DimRegion table has a RegionGroup column.
In Manage roles, create a role Sales_EU and set this filter on DimRegion:
[RegionGroup] = "EU"
And for Sales_US:
[RegionGroup] = "US"
This works, but does not scale. Every new region means a new role. That’s acceptable for:
If you expect growth or multiple tenants, do not stay in this pattern.
Dynamic RLS centralises the logic in a single security table and uses the current user’s identity to filter.
Create a table, e.g. SecurityUserRegion, with columns like:
UserPrincipalName (email / UPN).RegionKey (matching DimRegion[RegionKey]).RoleType, IsManager, etc.Sample structure:
CREATE TABLE SecurityUserRegion (
UserPrincipalName NVARCHAR(256) NOT NULL,
RegionKey INT NOT NULL,
RoleType NVARCHAR(50) NULL
);
Load this into your model and relate it:
SecurityUserRegion[RegionKey] ➜ DimRegion[RegionKey] (single-direction, SecurityUserRegion ➜ DimRegion).Create a role DynamicRegionRLS and set this filter on SecurityUserRegion:
SecurityUserRegion[UserPrincipalName] = USERPRINCIPALNAME()
How it works:
USERPRINCIPALNAME() returns the logged-in user.SecurityUserRegion to rows for that user.DimRegion and then to fact tables.You now have one role for all users. Adding a new user or region is a data operation (update SecurityUserRegion), not a model change.
Most real setups need different levels of access:
You can keep this in the same security table.
Add columns:
AccessLevel (e.g. "Staff", "Manager", "Global").CountryKey (for manager-level filters).Filter on SecurityUserRegion with conditional logic:
VAR CurrentUser = USERPRINCIPALNAME()
VAR UserRows = FILTER(
SecurityUserRegion,
SecurityUserRegion[UserPrincipalName] = CurrentUser
)
RETURN
// Allow multiple rows per user; the table filter is the result
UserRows
If you need different behaviour per AccessLevel, you can move some logic into calculated columns or views upstream, so that the RLS rule remains simple: filter by user and let the relationships + modelling handle the rest.
Resist the temptation to write complex IF logic directly in the role definition. Complex branching belongs in the data model, not in the RLS expression.
Multi-tenant means one dataset, multiple customers or business units, each seeing only their own data.
You typically have:
DimTenant (Tenant / Customer dimension).TenantKey.Key tables:
DimTenant(TenantKey, TenantName, Status, ...)FactSales(TenantKey, DateKey, ProductKey, ..., SalesAmount)SecurityUserTenant(UserPrincipalName, TenantKey, AccessLevel)Relationships:
SecurityUserTenant[TenantKey] ➜ DimTenant[TenantKey].DimTenant[TenantKey] ➜ FactSales[TenantKey].All relationships should be single-direction from security ➜ dimension ➜ fact.
Role filter on SecurityUserTenant:
VAR CurrentUser = USERPRINCIPALNAME()
RETURN
FILTER(
SecurityUserTenant,
SecurityUserTenant[UserPrincipalName] = CurrentUser
)
This gives:
If you need tenant admins who see only their tenant but all users within it, keep that logic in your app or upstream identity system; RLS should stay focused on data rows, not user management.
In many setups, you need both:
You can layer this without exploding complexity.
Use two security tables:
SecurityUserTenant
UserPrincipalNameTenantKeySecurityUserOrg
UserPrincipalNameTenantKeyOrgUnitKey or RegionKeyAccessLevelRelationships:
SecurityUserTenant[TenantKey] ➜ DimTenant[TenantKey].SecurityUserOrg[TenantKey] ➜ DimTenant[TenantKey].SecurityUserOrg[OrgUnitKey] ➜ DimOrgUnit[OrgUnitKey].In the RLS role, set filters on both tables:
// On SecurityUserTenant
SecurityUserTenant[UserPrincipalName] = USERPRINCIPALNAME()
// On SecurityUserOrg
SecurityUserOrg[UserPrincipalName] = USERPRINCIPALNAME()
How it behaves:
SecurityUserTenant ensures tenant isolation.SecurityUserOrg narrows within tenant by org unit / region.Important: avoid bi-directional relationships here; they make RLS behaviour harder to reason about and debug.
Multi-tenant models often share dimensions (e.g. DimDate, DimProduct) across tenants. That’s fine as long as:
TenantKey.DimDate is usually tenant-neutral. It should not be filtered directly by RLS. Instead, RLS filters facts, and those facts filter dates.
For dimensions like DimProduct, you might have:
TenantKey on DimProduct.DimTenant ➜ DimProduct ➜ FactSales.This ensures that when a user’s tenant is filtered, they only see that tenant’s products and facts.
Common mistakes:
DimCurrency) used by multiple tenants but not tenant-filtered.Mitigation options:
TenantKey to shared dimensions where it makes sense.Total Sales (Tenant) =
VAR CurrentTenant = SELECTEDVALUE(DimTenant[TenantKey])
RETURN
CALCULATE(
SUM(FactSales[SalesAmount]),
DimTenant[TenantKey] = CurrentTenant
)
RLS bugs are often subtle. A few practical habits:
In Power BI Desktop:
Check:
Create a hidden page with:
SecurityUserTenant filtered by current user.SecurityUserOrg filtered by current user.USERPRINCIPALNAME(), USERNAME(), TenantKey.Example measure:
Current User = USERPRINCIPALNAME()
Use this page when debugging with View as or in the service (with test accounts).
Performance issues often come from:
SecurityUserTenant tables with no indexing upstream.Mitigate by:
UserPrincipalName is indexed in the source system.RLS is not a one-time setup. Treat it like code.
At minimum, keep:
Manual updates to SecurityUserTenant won’t scale. Options:
RLS controls what rows a user sees, not which reports they can access.
A user should never gain access to a report via RLS; they must already have access via workspace/app permissions.
Design your RLS around data-driven security tables, not around hard-coded roles. If you can map users to tenants, regions, and access levels in tables and keep your RLS expressions as simple “filter by current user” rules, you’ll get a model that is easier to maintain, safer to extend, and ready for multi-tenant scenarios without rewrites.
Power BI
New
Next Batches Now Live
Power BI
SQL
Power Apps
Power Automate
Microsoft Fabrics
Azure Data Engineering