Outcome
By the end of this lesson, you will be able to explain the placeholder pattern for gated widgets, identify which widgets each role can see, and explain why a denied widget keeps its slot instead of disappearing.
| Prerequisites | M4.1.L1-L6 (RBAC fundamentals) · M4.7.L1-L2 (presets, cloning) |
| Time | 9 minutes |
1. Concept
Some widgets show things not everybody should see: budgets, recommendations, schedule history.
Permissions apply per widget rather than per dashboard. You build one dashboard, everybody gets the same layout, and each person sees the widgets they are entitled to and a placeholder for the rest.
EXAMPLE: Dashboard with widgets gated on different policies:
WIDGETS ON DASHBOARD REQUIRES ────────────────────────────────────────────── Cost trend report:view Top costly resources report:view Cost saving opportunities recommendation:view Budget health budget:view Schedule activity state-history:view Upcoming actions schedule:view
WHAT EACH ROLE SEES:
Viewer, Editor or Admin (system roles): All six widgets shown (every system role holds every *:view policy except billing:view, so no dashboard widget is gated for them)
Finance Partner (custom role: report:view + budget:view only): Cost trend, Top costly shown Cost saving opportunities placeholder Budget health shown (the custom role grants it) Schedule activity placeholder Upcoming actions placeholderThe practical lesson: placeholders appear for custom roles. If you only use the three system roles, every widget renders for everyone, and per-widget RBAC matters the day you write your first narrow custom role.
How it works
DECLARATION: Each widget declares one required policy in the widget registry. Example: budget_health.requiredPolicy = "budget:view"
RENDERING: When the dashboard renders, for each widget: if user.has(widget.requiredPolicy): render widget normally (fetch data, display) else: render the "no permission" placeholder in the same slot (the widget component never mounts, so it makes no API call)
SECURITY: The frontend gating decides what to render The gateway still enforces: if the user somehow bypasses the gate and calls the widget's data API, the gateway rejects. Defense in depth (M4.1.L6).Widget-to-policy mapping
All 13 widget types and the policy each one needs:
WIDGET REQUIRED POLICY──────────────────────────────────────────────────────────────────Key metrics report:viewCost trend report:viewCloud spend breakdown report:viewCost reports report:viewTop costly resources report:viewShowback report:viewScheduler success rate report:viewCost saving opportunities recommendation:viewQuick insights recommendation:viewBudget health budget:viewSchedule execution state-history:viewSchedule activity state-history:viewUpcoming actions schedule:viewFive policies cover every widget. A custom role that should read a whole dashboard needs only the policies its widgets list. The same table drives the widget picker: when you edit a dashboard, a widget whose policy you lack is shown disabled in the Add widget drawer.
The “no permission” placeholder
When a widget is gated, the UI renders a short card in the widget’s slot:
┌─────────────────────────────────────────────┐│ Budget health ││ ││ You don't have permission to view this ││ widget. ││ │└─────────────────────────────────────────────┘The placeholder names the widget and says the user lacks permission. It does not name the missing policy or the user’s role, so the admin works that out from the widget-to-policy table above. It is still much better than an empty widget, which leaves the user confused about why they see nothing.
No hide option
There is no setting to hide a denied widget. A widget the viewer cannot see always keeps its slot and shows the placeholder, so every viewer’s layout matches.
WHY ALWAYS PLACEHOLDER: Layout is identical for every viewer of the dashboard Two people discussing "the third widget" mean the same one Users see what exists and can ask for access to it No per-role layout to configure, review or get wrong
IF A WIDGET IS IRRELEVANT TO AN AUDIENCE: Don't rely on hiding: build that audience its own dashboard without the widget (clone, remove, save; L2)Why frontend gating + backend enforcement
The placeholder is a frontend decision; the gateway is the security boundary. Both exist for distinct reasons:
FRONTEND PLACEHOLDER: UX: don't show empty widgets / 403 errors Performance: don't issue API calls that will fail Transparency: tell the user something is there
BACKEND GATEWAY: Security: actually enforce that the user can't fetch the data Defense in depth: if the frontend has a bug, the gateway still prevents data leakA scripted client that ignores the frontend can still attempt to fetch the widget’s data; the gateway rejects with 403. The frontend gate is a UX nicety; the gateway is the security control.
What you control
CUSTOMER CONTROLS: Role assignment (M4.1.L2-L3): which policies a user holds Dashboard composition (L2): which widgets a dashboard carries
ZOPNIGHT-MANAGED: The widget registry declares the required policy per widget Customers cannot override it (it's a security decision, not a customization decision)How ZopNight uses widget RBAC
The widget registry is in code; each entry carries a requiredPolicy. The dashboard renderer checks the viewer’s permission for each slot and renders either the widget or the placeholder. A widget type the frontend does not recognise also gets a placeholder rather than breaking the page, and a widget that throws an error is caught by its own error boundary.
2. Demo
A team dashboard rendered for three different roles:
DASHBOARD: "Platform Eng Daily" (cloned from the FinOps preset)WIDGETS: 1. Key metrics (report:view) 2. Cost trend (report:view) 3. Cost reports (report:view) 4. Budget health (budget:view) 5. Top costly resources (report:view) 6. Cost saving opportunities (recommendation:view) 7. Schedule activity (state-history:view) 8. Scheduler success rate (report:view)
VIEW A: Admin user: All 8 widgets shown with data
VIEW B: Viewer (junior, just hired): All 8 widgets shown with data Cost saving opportunities is read-only: Viewer can see recommendations but not act on them
VIEW C: Contractor (custom role: report:view only): Widgets 1, 2, 3, 5, 8: shown with data Widget 4 (Budget health): placeholder Widget 6 (Cost saving opportunities): placeholder Widget 7 (Schedule activity): placeholder Layout: identical (each placeholder occupies the same slot)
OBSERVATIONS: - Same dashboard URL renders differently per role - Layout consistent (no shifting widgets per role) - The contractor sees that budget, recommendation and schedule widgets exist, and can ask for the policies if the work needs themThe placeholder pattern is what makes one dashboard usable for everyone.
3. Hands-on (5 min)
Verify per-widget RBAC on your dashboard:
DASHBOARD reviewed: __________
WIDGETS on it and the policy each needs (use the table above): □ __________ (policy: __________) □ __________ (policy: __________) □ __________ (policy: __________)
CUSTOM ROLES in your org (Settings → Roles): Role: __________ Missing policies: __________ Widgets that will show a placeholder for it: __________
If you have admin access: Ask a user with that custom role to open the dashboard Confirm the placeholders match your prediction
FOR EACH placeholder: □ Correct (the role should not see it) □ Wrong (add the policy to the role, or build the audience its own dashboard without the widget)If your org uses only the Viewer, Editor and Admin roles, write down which widgets a planned custom role would lose before you create it.
4. Knowledge check
Q1
A user whose custom role lacks budget:view opens a dashboard with the Budget health widget. What do they see?
A. An empty widget area where the panel used to be, with no explanation of why it is gone
B. A raw 403 error message rendered directly inside the widget frame itself, unstyled
C. A placeholder in the same slot, naming the widget and saying they lack permission to view it
D. The widget disappears entirely and the rest of the grid reflows around the gap it leaves
Show answer
Correct: C. The layout stays the same as an admin’s view; only the data is gated. The widget never mounts, so it makes no API call and there is no 403 in the logs. The placeholder does not name the missing policy; the admin reads that from the widget-to-policy table.
Q2
An admin wants a widget to disappear entirely for viewers who cannot see it. What is available?
A. A per-widget Hide setting in the dashboard editor
B. Nothing: denied widgets always show the placeholder
C. A per-role layout override set on the Roles page
D. A hide flag set by support on the org’s settings
Show answer
Correct: B. There is no hide option. A denied widget always keeps its slot and shows the placeholder, so every viewer’s layout matches. If a widget is irrelevant to an audience, build that audience its own dashboard without it.
Q3
The policy a widget needs is decided by:
A. The widget registry, which sets one required policy per widget that customers cannot override
B. A manual setting made per user on each dashboard that they are able to open
C. A per-widget allow-list that is maintained by the organisation’s admins by hand
D. Per-dashboard custom roles, created separately for each dashboard by an administrator
Show answer
Correct: A. ZopNight sets the policy per widget in the registry. Customers control the other two layers: which policies a user holds (role assignment) and which widgets a dashboard carries (composition).
5. Apply
Per-widget RBAC is automatic; no per-dashboard configuration is required to enable it, and there is no per-widget visibility setting to manage. Before you roll out a custom role, check it against the widget-to-policy table so its holders don’t meet a wall of placeholders on the org default.
For multi-role customer demos, show the same dashboard from an Admin’s window and from a narrow custom role’s window: the placeholder pattern is a clear demonstration of the RBAC model in action.
Related lessons
- L1: Four presets
- L2: Cloning and customization
- L4: Default dashboard governance (next)
- T4.M4.1.L6: Frontend gating with usePermission