DAX & Semantic Model Standards
Standards for DAX measure documentation, naming conventions, formatting, and semantic model organization. This is a companion to the Git & Azure DevOps Guide — that document covers the development workflow; this one covers what good Power BI code looks like.
Table of Contents
- Why This Matters
- Measure Documentation
- Measure Naming Conventions
- DAX Formatting & Style
- Display Folders
- Semantic Model Hygiene
- Code Review Checklist for DAX
1. Why This Matters
These standards exist for three reasons:
- Handoff and continuity. When someone else picks up your report — or you come back to it six months later — undocumented measures with cryptic names are a time sink. Clear names and descriptions eliminate guesswork.
- Code review quality. We are introducing peer review for all new DAX measures. Reviewers need to be able to read and understand your logic quickly. Consistent formatting and commenting makes that possible.
- Automated validation. We plan to add a Claude agent that scans PRs for DAX best practices. These standards define what "best practices" means for our team so the automation has clear rules to enforce.
2. Measure Documentation
Every DAX measure must be documented. No exceptions.
Measure description (required for every measure)
The description field is visible in Power BI tooltips, the field list, and to anyone browsing the semantic model. Write it as a short sentence that a business user could understand.
In Power BI Desktop: Right-click the measure → Properties → Description field.
In TMDL files (when editing directly): The description is a property on the measure object.
✅ "Total occupied units divided by total available units, expressed as a percentage."
✅ "Year-to-date count of new move-ins, using fiscal year starting April 1."
✅ "Variance between actual and budgeted revenue. Positive = over budget."
❌ "Calculates occupancy" ← too vague
❌ "" ← empty — never acceptable
❌ "DIVIDE(SUM(...), SUM(...))" ← just restates the formula
Rules for descriptions:
- Write for a business user, not a developer
- State what the measure calculates and any important context (time frame, filter behavior, what positive/negative means)
- If the measure behaves differently depending on filter context, say so
Inline DAX comments (required for non-trivial measures)
For any measure longer than a single line, or any measure with business logic that is not immediately obvious, add comments explaining why the calculation works the way it does. Comments explain the reasoning — the code already shows the what. Claude is a great tool for generating these Inline comments, Give this document to Claude and ask it to remember you want to add these comments to all future measures.
Occupancy Rate =
// Occupied units as a percentage of total available units.
// Uses CALCULATE to override any page-level property type filter
// so the denominator always reflects the full portfolio.
VAR _OccupiedUnits =
SUM( Units[Occupied] )
VAR _TotalUnits =
CALCULATE(
SUM( Units[Total] ),
ALL( Units[Property Type] ) // Remove property type filter for denominator
)
RETURN
DIVIDE( _OccupiedUnits, _TotalUnits, 0 ) // 0 if no units exist
When comments are required:
- Any use of CALCULATE with filter modification (ALL, REMOVEFILTERS, KEEPFILTERS, USERELATIONSHIP)
- Time intelligence logic (SAMEPERIODLASTYEAR, DATEADD, DATESYTD, etc.)
- Business rules that are not obvious from the formula (fiscal year start dates, custom period definitions, specific exclusion logic)
- Any workaround or non-obvious pattern
When comments are optional:
- Simple aggregations:
Total Revenue = SUM( Sales[Revenue] ) - Single-line measures where the name and description fully explain the logic
3. Measure Naming Conventions
| Convention | Example |
|---|---|
| Use plain business language | Total Revenue, Occupancy Rate, YTD Move-Ins |
| Avoid abbreviations unless universally understood | YTD ✅, Occ Rt ❌ |
| Time-intelligence suffixes | Revenue YTD, Revenue PY, Revenue YoY % |
Percentage measures end with % | Occupancy Rate %, Profit Margin % |
| Avoid technical prefixes | Total Sales ✅, msr_TotalSales ❌ |
| Do not conflict with DAX function names | Total Sales ✅, Sum ❌ |
Standard time-intelligence suffixes
Use these consistently across all reports:
| Suffix | Meaning | Example |
|---|---|---|
YTD | Year-to-date | Revenue YTD |
PY | Prior year (same period) | Revenue PY |
YoY | Year-over-year change | Revenue YoY |
YoY % | Year-over-year change as percentage | Revenue YoY % |
MTD | Month-to-date | Revenue MTD |
QTD | Quarter-to-date | Revenue QTD |
LM | Last month | Revenue LM |
Rolling 12 | Rolling 12-month total | Revenue Rolling 12 |
Table and column naming
| Convention | Example |
|---|---|
| Table names use plain nouns with spaces | Sales, Units, Property, Calendar |
| No technical prefixes on user-facing tables | Sales ✅, fctSales ❌, dim_Property ❌ |
| Hidden/internal tables may use PascalCase | PricingConfiguration (if hidden from report users) |
| Column names use plain language with spaces | Property Type, Move In Date, Unit Count |
4. DAX Formatting & Style
Variable naming
Use PascalCase with an underscore prefix to distinguish variables from table columns and measures:
VAR _TotalSales = SUM( Sales[Amount] )
VAR _PreviousYearSales = CALCULATE( [Total Sales], SAMEPERIODLASTYEAR( Calendar[Date] ) )
VAR _YoYChange = _TotalSales - _PreviousYearSales
RETURN
DIVIDE( _YoYChange, _PreviousYearSales, 0 )
Why the underscore prefix? It makes variables instantly distinguishable from measures and columns in complex formulas. When you see _TotalSales you know it is a local variable; when you see [Total Sales] you know it is a measure reference.
Use variables for readability
Break complex measures into named steps. Even if a variable is only used once, it makes the logic easier to follow and debug. When Copying and pasting logic make sure to update variable names to remain relevant.
// ❌ Hard to read — nested functions with no explanation
Net Absorption =
CALCULATE( SUM( Activity[Units] ), Activity[Type] = "Move-In" ) -
CALCULATE( SUM( Activity[Units] ), Activity[Type] = "Move-Out" )
// ✅ Clear — each step is named
Net Absorption =
VAR _MoveIns =
CALCULATE( SUM( Activity[Units] ), Activity[Type] = "Move-In" )
VAR _MoveOuts =
CALCULATE( SUM( Activity[Units] ), Activity[Type] = "Move-Out" )
RETURN
_MoveIns - _MoveOuts
Division: always use DIVIDE
Never use the / operator for division. Use DIVIDE() with an alternate result to handle division by zero gracefully.
// ❌ Will error or return Infinity on zero denominator
Occupancy Rate % = SUM( Units[Occupied] ) / SUM( Units[Total] )
// ✅ Returns 0 (or BLANK) when denominator is zero
Occupancy Rate % = DIVIDE( SUM( Units[Occupied] ), SUM( Units[Total] ), 0 )
Indentation and line breaks
- One argument per line for functions with multiple arguments
- Indent nested functions
- Align RETURN at the same level as VAR
Revenue YoY % =
VAR _CurrentYear =
SUM( Sales[Revenue] )
VAR _PriorYear =
CALCULATE(
SUM( Sales[Revenue] ),
SAMEPERIODLASTYEAR( Calendar[Date] )
)
RETURN
DIVIDE(
_CurrentYear - _PriorYear,
_PriorYear,
BLANK()
)
5. Display Folders
Group related measures into display folders so the field list is navigable. Without folders, a model with 50+ measures becomes unmanageable.
Set display folders in Power BI Desktop: Select the measure → Properties → Display Folder.
Folder structure pattern
Organize by business domain, not by technical type:
📁 Revenue
Total Revenue
Revenue YTD
Revenue PY
Revenue YoY %
📁 Occupancy
Occupancy Rate %
Total Units
Occupied Units
Vacant Units
📁 Move Activity
YTD Move-Ins
YTD Move-Outs
Net Absorption
📁 Budget
Budget Revenue
Budget Variance
Budget Variance %
Rules:
- Every measure must be in a display folder — no orphan measures floating at the root
- Folder names use plain business language (match how the client talks about the data)
- Helper measures (intermediate calculations not shown on reports) go in a
📁 _Utilityfolder and are hidden from the report view
6. Semantic Model Hygiene
These are general model health practices that make the semantic model easier to maintain and review.
Hide what users don't need
- Hide foreign key columns (e.g.,
PropertyIDon the fact table) — users should navigate via relationships, not key columns - Hide intermediate/staging tables that are only used for calculations
- Hide columns that exist only for relationships or internal logic
Prefer measures over calculated columns
Use measures for any value that should aggregate or respond to filter context. Calculated columns consume memory and recalculate on refresh — measures calculate on demand.
Use a calculated column when:
- You need to sort or filter by the value
- The value depends only on the current row (row context)
Use a measure when:
- The value should change based on slicers, filters, or visual context
- You are aggregating, counting, or calculating ratios
Relationships
- Use single-direction relationships wherever possible
- Avoid bidirectional filtering unless there is a clear and documented reason
- If bidirectional is required, add a comment in the relationship's table TMDL file explaining why
7. Code Review Checklist for DAX
When reviewing a PR that contains new or modified measures, check the following:
| Check | Pass criteria |
|---|---|
| Description filled in | Every new measure has a non-empty, business-readable description |
| Inline comments | Non-trivial measures explain why, not just what |
| Naming convention | Plain business language, standard time-intelligence suffixes, no technical prefixes |
| Display folder assigned | Measure is in an appropriate folder, not floating at root |
| DIVIDE used for division | No raw / operators — DIVIDE() with alternate result |
| Variables used for clarity | Complex logic is broken into named VAR steps |
| Filter context correct | CALCULATE modifiers (ALL, REMOVEFILTERS, etc.) are intentional and commented |
| Error handling | Edge cases handled (empty tables, zero denominators, BLANK values) |
| No FILTER on fact tables | Use CALCULATE with column predicates instead of FILTER( FactTable, ... ) where possible |
| Hidden appropriately | Helper/utility measures are hidden from report view |