The bottom line
For multi-plant reporting, do not build a report per plant — build one model with dynamic row-level security. Define one role whose DAX filter maps the signed-in user (USERPRINCIPALNAME) to their plant via a user-mapping table, so every plant manager sees only their rows from the same report. Use object-level security to hide sensitive columns entirely, secure it in the semantic model rather than the intranet page, and test with Test as role before publishing. One model, secured once, every report inherits it.
In This Article
The Copy-Per-Plant Trap
The instinct for multi-plant reporting is to give each plant its own copy of the dashboard, filtered to their site. It works in a demo and fails in production the first time someone publishes the wrong copy, or a new plant is added and its report is forgotten, or a manager is emailed a link to a plant that is not theirs.
Per-report security is a maintenance and leakage problem waiting to happen. Every new plant multiplies the reports; every change has to be made in each copy; and the access model lives in who-was-sent-which-link rather than in the data.
The correct pattern is the opposite: one model, one report, and the security applied at the data layer so each user sees only their rows. That is row-level security, and for multi-plant it should be dynamic.
Per-plant report copies are a leakage problem waiting to happen — the access model lives in who-was-sent-which-link. One model with row-level security puts it in the data instead.
What RLS Actually Does
Row-level security restricts data access for specific users of a Power BI semantic model. You define roles, each with a DAX filter expression that evaluates true or false for every row; only rows that return true are visible to a user in that role. Everything else is completely removed — not hidden in the visual, removed from the query.
A crucial detail for governance: RLS applies to users with the Viewer role in the workspace. Workspace Admins, Members and Contributors have edit permission, so RLS does not restrict them. If you want RLS to apply to someone, they get the Viewer role — that is the boundary Microsoft enforces.
RLS is supported on imported models, on DirectQuery, and on Direct Lake models in Microsoft Fabric — so the same pattern works whether your semantic model is Import or reading live from OneLake.
Static vs Dynamic — Use Dynamic
There are two ways to write the filter. Static RLS hard-codes the value — a "West" role with the filter [Region] = "West". For a handful of fixed regions that is fine, but for multi-plant it means a role per plant and a re-edit every time a plant is added or a manager moves. That is the per-copy problem moved into roles.
Dynamic RLS is the multi-plant answer. One role, with a filter that maps the signed-in user to their data at query time: [UserEmail] = USERPRINCIPALNAME(), where USERPRINCIPALNAME() returns the signed-in user's identity from Microsoft Entra ID. A single role definition then filters differently for every user, driven by a user-mapping table in the model.
Microsoft is explicit that dynamic RLS is the most common approach precisely because one role scales to every user, based on the mapping table, rather than one role per value. For multi-plant, that is the whole game — add a plant or move a manager by editing a table, not the model.
Dynamic RLS is the multi-plant answer: one role, filter [UserEmail] = USERPRINCIPALNAME(), driven by a user-mapping table. Add a plant or move a manager by editing a table — not re-editing the model.
The Multi-Plant Build, Step by Step
The build is short. First, add a user-mapping table to the model — one row per user with their email (matching their Entra ID sign-in) and the plant(s) they are allowed to see. A manager over two plants gets two rows.
Second, in Power BI Desktop, Modeling → Manage Roles, create one role — call it "Plant Manager" — with a DAX filter on the user-mapping table: [UserEmail] = USERPRINCIPALNAME(). Relate the mapping table to your plant dimension so the filter flows through to the fact tables. Now the signed-in user's row in the mapping table filters every plant they are not mapped to out of the entire model.
Third, publish, and in the Power BI service assign the actual users (or an Entra security group) to the role — as Viewers. Fourth, validate with Test as role before anyone relies on it. One caution from the docs: Test as role uses your own identity for USERPRINCIPALNAME(), so for a true check of another user's view, confirm the mapping table has their exact UPN, and where possible verify with a real Viewer account.
Hiding Columns With Object-Level Security
RLS filters rows, not columns. If a plant manager may see their plant's rows but must not see a cost or margin column at all, RLS is the wrong tool — Microsoft is clear that if a user can see a row, they can see every column of that row. The right tool is object-level security (OLS), which removes entire columns or tables from a role.
The two work together. RLS gives the plant manager their plant's rows; OLS ensures the sensitive columns — unit cost, margin, supplier price — are not in their role at all, so the field simply does not exist for them. Combined, a plant manager sees their plant's operational data and never the group's commercial data, from the same model.
This is the distinction that trips teams up: "they should see their plant but not the cost column" is two mechanisms — RLS for the rows, OLS for the column — not one.
The Intranet Page Is Not the Control
If the dashboard is embedded on an intranet page, the security must still be enforced in the semantic model, not by the page. Hiding a visual with page-level markup, or trusting that only the right people have the link, is cosmetic — the data is one view-source or one shared link away.
For embedding, this means passing the real user identity so RLS applies per person. Embedding for your organisation uses the signed-in user's identity, so their RLS role is enforced. Embedding with a single service account (embed for your customers) bypasses per-user RLS unless you explicitly pass an effective identity through the API — Microsoft notes that service principals do not resolve USERPRINCIPALNAME() to an end user, so per-user filtering needs the effective-identity mechanism.
The rule is simple: enforce security where the data is, not where it is displayed. A governed model with RLS and OLS is secure whether it is viewed in the Power BI app, in Excel via Analyze in Excel, or embedded in the intranet — because the restriction travels with the model.
Enforce security where the data is, not where it is displayed. A page-level hide is cosmetic; RLS and OLS in the model travel with the data into the app, Excel and the intranet embed alike.
So What — the Pattern
For multi-plant reporting, the pattern is one governed semantic model with dynamic RLS: a user-mapping table, one role filtering on USERPRINCIPALNAME(), sensitive columns removed with object-level security, users assigned as Viewers, and validation with Test as role plus a real account. Add a plant or move a manager by editing the mapping table, not the model.
This replaces the per-copy sprawl with a single asset that is secured once and inherited by every report built on it. New reports are secure automatically; the access model lives in the data, not in a list of who has which link.
On Microsoft Fabric the same model reads the gold layer in Direct Lake, so the multi-plant security sits on live operational data. Secure the model, and the intranet page, the Excel export and the mobile app are all secured with it.
One governed model, dynamic RLS on a mapping table, OLS for sensitive columns, users as Viewers, tested before publish. Add a plant by editing a table. Secure the model and every surface inherits it.
If your multi-plant reporting is a stack of per-plant report copies, or sensitive columns are visible to people who should only see their site, that is a security gap worth closing properly. 30 minutes with Amit on your model — dynamic RLS, object-level security, embedding identity — and what securing the model once, not the page, would change. No slides. No pitch deck. No obligation to proceed.
Free Assessment
Where does your operation sit on the data maturity curve?
8 questions. 3 minutes. You get a scored breakdown across data infrastructure, analytics readiness, and automation potential — with a specific next step for your industry.