Build a Power BI risk dashboard from your risk register: data model, DAX measures, a 5x5 heat map without custom visuals, and monthly trend tracking.
A good Power BI risk dashboard needs three things: a clean risk table with numeric scores, a handful of DAX measures, and monthly snapshots so you can show whether exposure is falling. The visuals are the easy part. Most risk dashboards fail because the data underneath was built for a spreadsheet, not for a model.
This guide walks through the whole build, from shaping your risk register to a finished one-page report. You'll get the data model, the DAX measures, a 5x5 heat map that needs no custom visuals, and a way to track trends over reporting periods.
It's written for project controls, PMO and BI teams who already know their way around Power BI Desktop.
What a Power BI risk dashboard should answer
Before opening Power BI, agree the four questions your audience asks every month. Each one maps to one visual, and anything that doesn't answer one of them belongs on a drill-through page.
| Question the reader asks | Visual | Key measure |
|---|---|---|
| How much risk are we carrying? | KPI cards across the top | Open risks, high risks, total residual exposure |
| Where is it concentrated? | 5x5 heat map | Risks per likelihood and impact cell |
| Is it getting better or worse? | Line chart by reporting period | Residual exposure per snapshot |
| What needs action now? | Top 10 table | Residual score, owner, overdue actions |
Keep it to one page. Boards read the first screen and rarely scroll.
Step 1: Shape your risk register data
Power BI needs one row per risk and numbers where your spreadsheet probably has words. If your register scores risks as "High" or "Amber", convert them to 1 to 5 scales first. Ratings can be derived later; numbers can't be recovered from words.
You need three tables at minimum:
| Table | One row per | Required columns |
|---|---|---|
| Risks | Risk | RiskID, Title, Category, Owner, Status, InherentLikelihood, InherentImpact, ResidualLikelihood, ResidualImpact, DateRaised, NextReviewDate |
| Actions | Mitigation action | ActionID, RiskID, ActionOwner, DueDate, Status |
| RiskSnapshots | Risk per reporting period | SnapshotDate, RiskID, ResidualLikelihood, ResidualImpact |
In Power Query, fix the usual spreadsheet problems before they reach the model:
- Merged cells and multi-row headers. Promote one clean header row and fill down.
- Several registers in one workbook. Append them into one Risks table and add a Project or BusinessUnit column.
- Actions typed into a notes column. Split them into the Actions table, keyed on RiskID. Overdue actions are the most useful number on the page, and you can't count them from free text.
- Text in date columns. Set the data type explicitly and route errors to a review query rather than letting them become blanks.
If the register itself keeps falling behind, that's a different problem: see why risk registers go stale.
Step 2: Build the data model
Risks is the hub. Actions and RiskSnapshots each relate to it on RiskID, one to many, and a Date table relates to SnapshotDate so time intelligence works on the trend.
Create the Date table, add Month and Year columns, and mark it as a date table:
Date = CALENDAR ( DATE ( 2024, 1, 1 ), DATE ( 2027, 12, 31 ) )Keep relationships single-direction. Bi-directional filters make the KPI cards return confusing counts when a slicer touches both actions and risks.
Owner and Category can stay as columns on Risks for a single register. If you report across several projects that share owners, move them into their own lookup tables.
Step 3: Write the core DAX measures
Start with two calculated columns on the Risks table. Scores are fixed per row, so columns are fine here; everything else should be a measure.
Inherent Score = Risks[InherentLikelihood] * Risks[InherentImpact]
Residual Score = Risks[ResidualLikelihood] * Risks[ResidualImpact]
Residual Rating =
SWITCH (
TRUE (),
Risks[Residual Score] >= 15, "High",
Risks[Residual Score] >= 8, "Medium",
"Low"
)The 15 and 8 thresholds are a common split on a 5x5 matrix. Use the bands from your own risk policy so the dashboard matches what the board already knows.
Then the measures that drive the KPI cards:
Open Risks =
CALCULATE ( COUNTROWS ( Risks ), Risks[Status] <> "Closed" )
High Risks =
CALCULATE ( [Open Risks], Risks[Residual Rating] = "High" )
Residual Exposure =
CALCULATE ( SUM ( Risks[Residual Score] ), Risks[Status] <> "Closed" )
Risk Reduction % =
VAR Inherent = CALCULATE ( SUM ( Risks[Inherent Score] ), Risks[Status] <> "Closed" )
RETURN DIVIDE ( Inherent - [Residual Exposure], Inherent )
Overdue Actions =
CALCULATE (
COUNTROWS ( Actions ),
Actions[Status] <> "Complete",
Actions[DueDate] < TODAY ()
)
Reviews Overdue =
CALCULATE ( [Open Risks], Risks[NextReviewDate] < TODAY () )Risk Reduction % is the one executives remember. It shows how much of the raw exposure your controls are taking off the table: the gap between inherent risk (before controls) and residual risk (after them).
Step 4: Build a 5x5 risk heat map without custom visuals
The heat map is a standard Matrix visual with conditional formatting. The trick is two small axis tables, so every one of the 25 cells shows even when it holds no risks. Create them under Modeling > New table:
Likelihood = GENERATESERIES ( 1, 5, 1 )
Impact = GENERATESERIES ( 1, 5, 1 )Leave both disconnected from the rest of the model. Then add two measures:
Risks in Cell =
VAR L = SELECTEDVALUE ( Likelihood[Value] )
VAR I = SELECTEDVALUE ( Impact[Value] )
RETURN
CALCULATE (
[Open Risks],
Risks[ResidualLikelihood] = L,
Risks[ResidualImpact] = I
) + 0
Cell Colour =
VAR Score = SELECTEDVALUE ( Likelihood[Value] ) * SELECTEDVALUE ( Impact[Value] )
RETURN
SWITCH (
TRUE (),
Score >= 15, "#D64545",
Score >= 8, "#F2B134",
"#4CAF7A"
)To assemble the visual:
- Add a Matrix. Put Likelihood[Value] on Rows, Impact[Value] on Columns and [Risks in Cell] on Values.
- Sort the rows descending so likelihood 5 sits at the top, the way risk teams read a matrix.
- Under Cell elements, turn on Background color, choose Field value and select [Cell Colour]. Do the same for font colour if you want the numbers to blend in on empty cells.
- Turn off row and column subtotals, then rename the axis headers to "Likelihood" and "Impact".
- Expect one trade-off: because the axis tables are disconnected, clicking a cell won't cross-filter other visuals. Slicers on the page still filter the heat map, so use those, and let the top 10 table carry the detail.
The colour bands use the same 15 and 8 thresholds as the Residual Rating column. Keep them in step: a red cell that holds "Medium" risks will cost you credibility in the first board meeting. Cilver draws the same view for you as an inherent and residual risk matrix.
Step 5: Track risk trends with monthly snapshots
A risk register only knows today. To show whether exposure is falling, you need a copy of each risk's scores at every reporting date, and Power BI won't keep that for you: each refresh overwrites the last.
Three ways to capture snapshots, from simplest to most robust:
- Manual export. At each month-end, copy the register into a dated file in a SharePoint folder and load the folder with Power Query. Works, until someone forgets.
- Power Automate. A scheduled flow that copies current scores into a RiskSnapshots list on the first of each month.
- A warehouse table. A Fabric or SQL pipeline that appends a snapshot row per risk on a schedule. Best when you report across many projects.
Add a Residual Score column to RiskSnapshots the same way as on Risks, relate SnapshotDate to your Date table, and the trend measures are short:
Snapshot Exposure =
SUM ( RiskSnapshots[Residual Score] )
Exposure vs Prior Period =
VAR CurrentExposure = [Snapshot Exposure]
VAR PriorExposure =
CALCULATE ( [Snapshot Exposure], PREVIOUSMONTH ( 'Date'[Date] ) )
RETURN
CurrentExposure - PriorExposurePlot Snapshot Exposure on a line chart by month, and show Exposure vs Prior Period as a card with red or green formatting, filtered to the latest snapshot month so it doesn't sum every period. A falling line is the clearest evidence a risk process is working. Cilver keeps these as period-over-period snapshots, one click per reporting period.
Step 6: Lay out the risk dashboard page
Arrange the page in the order the reader's questions come, top to bottom:
- KPI strip across the top: Open Risks, High Risks, Residual Exposure, Risk Reduction %, Overdue Actions, Reviews Overdue.
- Heat map on the left, trend line on the right. Where the risk sits, and which way it's moving.
- Top 10 table along the bottom: Title, Owner, Residual Score, Residual Rating, Next Review Date and Overdue Actions, sorted by Residual Score.
- Slicers in a narrow left rail: Project, Category, Owner, Status.
A few rules keep it credible:
- One colour language. Red, amber and green mean the same thing on the heat map, the table and the cards. Use the brand palette for everything else.
- Show inherent next to residual in the top 10 table. A risk that started at 25 and sits at 20 tells a very different story from one that started at 20.
- Put "Data as of" on the page using the latest refresh or snapshot date. Boards stop trusting a dashboard the first time it shows stale numbers without saying so.
- No 3D, no gauges. They take space and answer none of the four questions.
For ready-made report pages, see our Power BI risk reporting templates.
Where spreadsheet-fed risk dashboards break
The dashboard above works. What usually fails is the register feeding it:
- Refresh breaks when someone adds a column, renames a sheet or saves a copy with "v2 final" in the name.
- History depends on discipline. Miss one month-end snapshot and the trend line has a hole you can't fill.
- No audit trail. When a score drops from 20 to 9, the dashboard can't say who changed it or why.
- Actions drift from risks. Once actions live in a separate tracker, overdue counts stop matching reality.
These are register problems, not Power BI problems, and they're the reason we built Cilver. It keeps risks, controls and actions in one live register, captures a snapshot for each reporting period in one click, records every change, and lets you connect Power BI to a live risk register, so the model in this guide reads from a source that's always current.
Build the dashboard on the spreadsheet you have today. When maintaining the data takes longer than reading the report, start a 14-day Cilver trial and point the same model at a live register.
FAQ
Can I build a Power BI risk dashboard from an Excel risk register?
Yes. Load the workbook from SharePoint or OneDrive so scheduled refresh works without a gateway, then shape it in Power Query as in Step 1. The main requirement is numeric likelihood and impact scores rather than text ratings.
How do I make a risk heat map in Power BI?
Use a standard Matrix visual with likelihood on rows and impact on columns, a count of open risks as the value, and a background colour set by a DAX measure. Two disconnected 1 to 5 tables keep all 25 cells visible. No custom visual is needed.
What KPIs should a risk dashboard show?
Open risks, high risks, total residual exposure, risk reduction from inherent to residual, overdue mitigation actions and overdue reviews. Add the change in exposure since the last reporting period once you have snapshots.
Should the dashboard use inherent or residual scores?
Lead with residual, because that's the exposure you're actually carrying. Show inherent beside it in the detail table and in the Risk Reduction % card, so readers can see what your controls are doing.
How often should a risk dashboard refresh?
Daily is plenty for most registers. Power BI Pro allows up to 8 scheduled refreshes a day and Premium or Fabric capacity up to 48 (Microsoft Learn: Configure scheduled refresh). Trend snapshots should still be monthly, matching your reporting cycle.