Fabric tmdl
Personal Claude Code config — skills, subagents, hooks, and rules for Microsoft Fabric and Power BI workflows on Windows. Cherry-pickable, no semver.
npx -y skills add wardawgmalvicious/claude-config --skill fabric-tmdlAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
TMDL (Tabular Model Definition Language) authoring rules for Fabric and Power BI semantic models. Use when editing .tmdl files, adding measures or columns to a semantic model, defining relationships or calculation groups, working in a PBIP definition/ folder, configuring Direct Lake partitions, or debugging TMDL validation errors. Covers syntax (tabs not spaces, /// descriptions, single-quoting names), DAX measure patterns, row-level security roles, calendar groups, and common gotchas.
SKILL.md
9.0 KB, as published. Nobody here has run it
TMDL Authoring Rules
Syntax Rules (MUST follow)
- TMDL uses tab indentation — every nesting level is exactly one tab (
\t), NOT spaces. Spaces cause validation errors.- PowerShell: use
`t - Bash: use
$'\t'or literal tabs
- PowerShell: use
- Objects declared by type + name:
table Customer,column ProductId,measure 'Total Sales' - Names with spaces or special chars (
.,=,:,') must be in single quotes:column 'Order Date' - Descriptions use
///placed ABOVE the object — do NOT use thedescriptionproperty //comments are NOT supported in TMDL- Do NOT add
lineageTagon new objects — it is auto-generated - Multi-line DAX must be enclosed in triple backticks (
```) - Place measures before columns in table definitions
formatStringis required on every measure- Never set
dataTypeon measures — it is inferred from DAX
Naming Conventions
- Tables: business-friendly, no
Fact/Dimprefixes. Plural for facts (Sales), singular for dimensions (Product) - Columns: readable with spaces (
Order Date,Unit Price) - Measures: clear patterns (
Total Sales,# Customers). Time intelligence:[measure],[measure (ly)],[measure (ytd)])
Column Rules
| Property | Rule |
|---|---|
dataType | Required. Use int64, decimal, string, dateTime, boolean. Avoid double |
sourceColumn | Must match partition source column name exactly |
isHidden | Set for ID columns, foreign keys, system columns |
summarizeBy | none for non-aggregatable numerics (IDs, postal codes, year numbers) |
isAvailableInMdx | false for hidden columns not used in sort-by or hierarchies |
sortByColumn | For text needing non-alphabetical sort (month names → month number) |
Measure & DAX Rules
- Always set
formatString— Currency:$#,##0.00| Percentage:0.00%| Integer:#,##0| Decimal:#,##0.00 - Use
DIVIDE()instead of/for safe division - Never use
IFERROR— causes performance degradation - Prefix
VARnames with_:VAR _totalSales = ... - Use
displayFolderto organize measures into logical groups - Add
///descriptions to explain business logic
Relationship Rules
fromColumn:= many-side (fact);toColumn:= one-side (dimension)- Create relationships BEFORE measures that depend on them
- Default:
crossFilteringBehavior: oneDirection; addbothDirectionsonly when needed isActive: falsefor role-playing dimensions; useUSERELATIONSHIP()in DAX- Both sides must have matching
dataType - Set
isKey: trueon dimension primary key columns - Hide foreign keys on fact tables (
isHidden: true) - No composite keys — use a single surrogate integer key
Calculation Groups
table 'Time Intelligence'
calculationGroup
calculationItem Current = SELECTEDMEASURE()
calculationItem YTD = CALCULATE(SELECTEDMEASURE(), DATESYTD('Date'[Date]))
column 'Time Intelligence'
dataType: string
partition 'Partition_Time Intelligence' = calculationGroup
calculationGroupkeyword has NO name — just the keyword indented under the table- Partition type must be
= calculationGroup(not= mor= calculated) - Use
formatStringDefinition(notformatString) for calc items that override measure format
Security Roles
role RegionalManager
modelPermission: read
tablePermission Sales = [Region] = "East"
modelPermission:required — usereadorreadRefresh- Assign users via Power BI REST API, not TMDL:
POST .../datasets/{id}/userswithrolesarray - Do NOT use
INFO.ROLES()/INFO.ROLEMEMBERSHIPS()via DAX — unreliable. Use the REST API.
Annotations
- Do NOT add
PBI_*annotations manually — they are Power BI internal metadata - Custom annotations are fine for documentation/tooling
- Syntax: blank line before the first annotation; blank line between annotations; same indent as peer properties
column 'Product Name'
dataType: string
sourceColumn: Product Name
annotation MyTool_Owner = analytics-team
model.tmdl Required Properties
model.tmdl is the root of the definition/ folder alongside database.tmdl, expressions.tmdl, functions.tmdl, relationships.tmdl, roles/, perspectives/, cultures/, and tables/.
model Model
culture: en-US
defaultPowerBIDataSourceVersion: powerBI_V3
discourageImplicitMeasures
sourceQueryCulture: en-US
dataAccessOptions
legacyRedirects
returnErrorValuesAsNull
defaultPowerBIDataSourceVersion: powerBI_V3 is required for Import-mode models — without it, Import from JSON supported for V3 models only.
Direct Lake Configuration
-
ALL partitions must use
EntityPartitionSource— no M/Power Query -
A named expression pointing to the Lakehouse/Warehouse must be defined before tables:
expression DL_Lakehouse = let Source = AzureStorage.DataLake("https://onelake.dfs.fabric.microsoft.com/<WorkspaceId>/<LakehouseId>", [HierarchicalNavigation=true]) in Source -
Each table partition references the expression:
partition Sales = entity mode: directLake source entityName: Sales schemaName: dbo expressionSource: DL_Lakehouse -
dataType: binarycolumns are NOT supported in Direct Lake -
Columns map directly via
sourceColumn— no transforms
Gotchas
| Issue | Cause | Fix |
|---|---|---|
InvalidLineType: Property! in database.tmdl | Bare compatibilityLevel: without database declaration | Start the file with database <Name> on line 1 |
Import from JSON supported for V3 models only | Missing defaultPowerBIDataSourceVersion | Add powerBI_V3 to model.tmdl |
| Spaces-for-tabs validation errors | Editor converted tabs | Force literal tabs; configure editor not to expand |
// comment ignored or invalid | Not supported | Use /// on line above the object (descriptions only) |
| Measure has wrong inferred type | dataType was set manually | Remove dataType from measures — always inferred |
Missing formatString errors | Measure without formatString | Always set per measure; use formatStringDefinition for dynamic |
| Calc item format ignored | Used formatString instead of formatStringDefinition | formatStringDefinition is DAX-based; only it overrides the selected measure's format |
| Broken report binding after column rename | Stale lineageTag left in place | Never edit lineageTag; let Power BI regenerate only on creation |
| Role members ignored | Authored member statically | Assign via Power BI REST API (POST datasets/{id}/users) |
INFO.ROLES() returns stale/missing data | Known DAX surface unreliability | Query membership via REST API |
| Calendar name collision | Name unique per-table but not per-model | Calendar names must be globally unique across the model |
| Direct Lake partition errors | binary column in source | Cast away in upstream Lakehouse/Warehouse; drop the column |
| Perspective appears empty in Power BI | No perspectiveTable children | Add at least one table + column/measure, or includeAll on a table |
model.bim and definition/ both present | Forgot to delete .bim after TMDL conversion | Remove model.bim; they are mutually exclusive |
| TMDL conversion fails | Old Microsoft.AnalysisServices.retail.amd64 | Upgrade NuGet package for TmdlSerializer |
| Hierarchy level references missing column | Column removed or renamed without updating level | level.column: must reference an existing same-table column |
PBI_* annotation edits revert | Power BI rewrites on save | Do not hand-author PBI internal annotations |
Additional reference
- Microsoft Learn: TMDL language overview
- Microsoft Learn: TMDL view in Power BI Desktop
- Microsoft Learn: Power BI Desktop project semantic model folder (PBIP)
- Companion references/REFERENCE.md: per-object property tables (
database,model,table,column,measure,relationship,hierarchy,partition,calculationGroup,role,perspective,cultureInfo,expression,function,dataSource,refreshPolicy,calendar,queryGroup), BIM ↔ TMDL conversion procedure, enum value lists, and a comprehensive MS Learn link bundle (TMDL syntax / TMDL view / PBIP folder / calculation groups / Direct Lake / DAX / TMSL / TOM).