AgentStack
SKILL verified MIT Self-run

Fabric Tmdl

skill-wardawgmalvicious-claude-config-fabric-tmdl · by wardawgmalvicious

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 n…

No reviews yet
0 installs
0 views
view→install

Install

$ agentstack add skill-wardawgmalvicious-claude-config-fabric-tmdl

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Fabric Tmdl? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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
  • 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 the description property
  • // comments are NOT supported in TMDL
  • Do NOT add lineageTag on new objects — it is auto-generated
  • Multi-line DAX must be enclosed in triple backticks ( ` )
  • Place measures before columns in table definitions
  • formatString is required on every measure
  • Never set dataType on measures — it is inferred from DAX

Naming Conventions

  • Tables: business-friendly, no Fact/Dim prefixes. 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 VAR names with _: VAR _totalSales = ...
  • Use displayFolder to 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; add bothDirections only when needed
  • isActive: false for role-playing dimensions; use USERELATIONSHIP() in DAX
  • Both sides must have matching dataType
  • Set isKey: true on 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
  • calculationGroup keyword has NO name — just the keyword indented under the table
  • Partition type must be = calculationGroup (not = m or = calculated)
  • Use formatStringDefinition (not formatString) for calc items that override measure format

Security Roles

role RegionalManager
	modelPermission: read
	tablePermission Sales = [Region] = "East"
  • modelPermission: required — use read or readRefresh
  • Assign users via Power BI REST API, not TMDL: POST .../datasets/{id}/users with roles array
  • 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:

``tmdl expression DL_Lakehouse = let Source = AzureStorage.DataLake("https://onelake.dfs.fabric.microsoft.com//", [HierarchicalNavigation=true]) in Source ``

  • Each table partition references the expression:

``tmdl partition Sales = entity mode: directLake source entityName: Sales schemaName: dbo expressionSource: DL_Lakehouse ``

  • dataType: binary columns 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 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](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).

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.