PBIR & agents

PBIP, PBIR and TMDL explained: inside a Power BI project

What is inside a Power BI project folder, file by file, from a real 347-visual report: PBIR for the report, TMDL for the model, and why Git and agents care.

9 min read

The file tree of the Colis Pro project: 8 pages, 347 visual.json files, 30 TMDL tables with 465 measures

A .pbix file is a black box. Change one colour, save, and Git records that a binary file changed. Nobody can tell what moved, and two people who edit the same report cannot merge their work.

A Power BI project (PBIP) saves the same report as a folder of text files: one file per page, one per visual, one per table. Microsoft now documents PBIP and PBIR as generally available, and since the September 2026 rollout PBIR is the default report format in Power BI Desktop and the Service.

This article opens a real one, file by file: Colis Pro, a free Power BI report built with an AI agent: 347 visuals on 8 pages (three of them hidden), 30 tables and 465 measures.

PBIX and PBIP in one picture

A .PBIX FILEA POWER BI PROJECT (.PBIP)ColisPro.pbixone binary filereportsemantic modeldataGit sees one changed file.No line to read, nothing to merge.File > Save asand backColisPro.pbippoints to the report folderColisPro.Report/PBIR: a JSON file per visualColisPro.SemanticModel/TMDL: a text file per table.pbi/cache.abfthe data: local, git-ignoredGit sees the lines that changed.Every file opens in a text editor.
Same report, two containers. Desktop converts in both directions, and only through File > Save as.

Three names, three layers:

  • PBIP is the project: a folder, with a small .pbip file that points to the report.
  • PBIR is the format of the report inside it: a definition folder of JSON files.
  • TMDL, the Tabular Model Definition Language, is the format of the semantic model: a definition folder of .tmdl text files.

PBIX is not going away: the general availability announcement (message center MC1465770, 1 September 2026) says so explicitly. Reports in the older PBIR-Legacy format, one big report.json, still open, and Power BI converts them to PBIR when you edit and save them.

The tree of a real project

FILEWHAT IT HOLDSColisPro/ColisPro.pbipopen this fileColisPro.Report/the report, in PBIRdefinition.pbirlink to the model (byPath).platformitem identity for Fabric GitStaticResources/images and themes: 39 filesdefinition/report.jsonthemes, report settingsversion.jsonPBIR versionbookmarks/5 bookmarkspages/pages.jsonpage order, landing page8a36ca2f8ead50f38a8e/one folder per page: 8page.jsonsize, background, interactionsvisuals/9c9200b29ce35634a557/one folder per visual: 347visual.jsonposition, type, query, formattingColisPro.SemanticModel/the model, in TMDLdefinition.pbismmodel format version.platformitem identity for Fabric GitdiagramLayout.jsonmodel diagram layout.pbi/editorSettings.jsoneditor settings, shareddefinition/database.tmdlcompatibility levelmodel.tmdlculture, order of the tablesrelationships.tmdl18 relationshipsexpressions.tmdldata folder parametercultures/fr-FR.tmdlQ&A linguistic metadatatables/one file per table: 30_Mesures.tmdlall 465 measuresThe Colis Pro project: 445 files. Apart from 13 PNG images, every one is plain text.
Folder names under pages and visuals are 20-character identifiers, the default naming in PBIR. Here, the Network page and its card of saturated pickup points.
FileWhat it holds in Colis Pro
ColisPro.pbipA pointer to ColisPro.Report, plus one setting, enableAutoRecovery
definition.pbirThe model the report uses: byPath to ../ColisPro.SemanticModel
definition/report.jsonBase theme CY26SU05, custom theme ColisPro.json, the list of registered images
pages/pages.jsonThe order of the 8 pages and the landing page
page.jsonOne per page: 1440 × 1080, background image, visual interactions
visual.jsonOne per visual, 347 in all
bookmarks/5 bookmarks, one per main page
StaticResources/13 PNG and 24 SVG images, 2 themes
.platformItem type, display name and logicalId, for Fabric Git integration
definition.pbismThe model's format version, 4.2
database.tmdl, model.tmdlCompatibility level 1606, culture fr-FR, a ref table line per table that fixes their order
tables/*.tmdl30 files, one per table: columns, measures, partitions
relationships.tmdl18 relationships, 6 of them inactive
expressions.tmdlDossierDonnees, the Power Query parameter that holds the data folder path
cultures/fr-FR.tmdlQ&A linguistic metadata, 1.3 MB of it
diagramLayout.jsonThe model diagram. Leave it to Desktop: Microsoft does not support editing it outside Desktop

PBIR: one folder per page, one per visual

In PBIR, the report is a tree, not a file. pages.json lists the pages in order. Each page folder holds a page.json and a visuals folder, and each visual folder holds a visual.json. The Colis Pro Overview alone has 72 visual folders.

Here is the card that counts saturated pickup points on the Network page, trimmed to the four parts that matter and condensed. Desktop writes one key per line, so the full file runs to 643 lines, most of them formatting.

JSON
{
  "$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/2.13.0/schema.json",
  "name": "9c9200b29ce35634a557",
  "position": { "x": 691, "y": 902, "z": 33000, "height": 100, "width": 180, "tabOrder": 33000 },
  "visual": {
    "visualType": "cardVisual",
    "query": {
      "queryState": {
        "Data": {
          "projections": [{
            "field": {
              "Measure": {
                "Expression": { "SourceRef": { "Entity": "_Mesures" } },
                "Property": "Points saturés"
              }
            },
            "queryRef": "_Mesures.Points saturés",
            "nativeQueryRef": "Points saturés",
            "displayName": "Points saturés"
          }]
        }
      }
    },
    "objects": {
      "value": [{
        "properties": {
          "fontSize":  { "expr": { "Literal": { "Value": "28D" } } },
          "fontColor": { "solid": { "color": { "expr": { "Literal": { "Value": "'#FFFFFF'" } } } } }
        },
        "selector": { "id": "default" }
      }]
    }
  }
}
  • position places the visual on the page in pixels, with z for stacking and tabOrder for keyboard order.
  • visualType names the visual. cardVisual is the new card: 167 of the 347 Colis Pro visuals are cards.
  • query binds the fields. This card reads one measure, Points saturés, from the _Mesures table. That name is the link to the model.
  • objects holds the formatting. Numbers carry a type suffix (28D), text sits in single quotes. The container (title, background, border) is formatted next to it, in visualContainerObjects.

Where each formatting property has to sit is a subject of its own, covered in the PBIR validator traps.

TMDL: one file per table

The model side reads like an indented outline. Each table file declares the table, then its columns, measures and partitions. Here is DimRegion.tmdl, trimmed to one column and its partition:

TMDL
table DimRegion
    lineageTag: bf19eef9-f1b0-5f2f-9594-3cf40a2fa46b

    column Region
        dataType: string
        lineageTag: 27ae4132-0ea3-59bd-a331-fa27b8f88533
        dataCategory: StateOrProvince
        summarizeBy: none
        sourceColumn: Region
        sortByColumn: Ordre

    partition DimRegion = m
        mode: import
        source =
                let
                    Source = Parquet.Document(File.Contents(DossierDonnees & "DimRegion.parquet")),

Properties are written name: value, a bare isHidden means true, and indentation decides what belongs to what (the files use one tab per level). Expressions follow an =: M for a partition, DAX for a measure. 18 of the 30 Colis Pro partitions are Power Query, 13 of them reading Parquet files. The other 12 are calculated tables, like the thresholds of our settings panel.

All 465 measures sit in a single table file, _Mesures.tmdl (3,919 lines), sorted into 17 display folders. This is the measure behind the card above:

TMDL
    measure 'Points saturés' = COUNTROWS ( FILTER ( VALUES ( DimPointDepot[PointID] ), [Occupation] >= [Seuil saturation] ) )
        formatString: #,0
        displayFolder: 40 Réseau
        lineageTag: 1a1db087-26dd-5beb-97f2-1c029674def2

formatString and displayFolder are the format and the display folder you set in Desktop's Properties pane. lineageTag is an identifier that Desktop generates.

What stays out of the folder you share

Two kinds of file live in .pbi subfolders and belong to your machine only:

  • .pbi/cache.abf, in the model folder: a backup of the model with its data, as it was when last edited. In our working copy of Colis Pro, it weighs 142 MB.
  • .pbi/localSettings.json, in both folders: settings for the current user and computer.

When you save as a project, Desktop writes a .gitignore that excludes exactly these two, unless one already exists in the folder or in the parent Git repository:

TEXT
**/.pbi/localSettings.json
**/.pbi/cache.abf

Without the cache, Desktop opens the model with its full definition and no data. That is why the Colis Pro download has no cache.abf and asks you to click Refresh first: the data loads from the Parquet files that ship with it.

Not everything under .pbi is private: editorSettings.json is meant to be shared, and Colis Pro commits it. And one leak is easy to miss: Microsoft warns that visual.json and bookmark files can store values from your data, such as a filter value or a slicer selection. Read the diff before you push to a public repository.

Why it matters

Readable diffs. Here is a real change from Colis Pro, between two snapshots of the shipments line chart on the Overview. Its axis moved from the short month name to a one-letter initial:

DIFF
-        "Property": "MoisCourt"
+        "Property": "Initiale"
       }
     },
-    "queryRef": "AxeMois.MoisCourt",
-    "nativeQueryRef": "MoisCourt",
+    "queryRef": "AxeMois.Initiale",
+    "nativeQueryRef": "Initiale",

In a .pbix, the same change is an opaque blob. Here, the folder name says which visual changed and the lines say which field.

Scripts. A batch change becomes a loop over files. Microsoft's own example is hiding the visual-level filters on every visual of a report.

AI agents. An agent reads text and writes text. It reads the TMDL to learn the tables, columns and measures, then writes visual.json files that bind them. Colis Pro was built that way.

Validation and render checks. Microsoft's offline validator, powerbi-report-author validate, checks the files against their schemas in seconds. It never draws anything, which is why it can pass while Desktop disagrees. The truth is the render: we have the agent drive Power BI Desktop and capture what it draws, a loop described in our Desktop Bridge article.

You, a script or an agentreads *.tmdl, writes visual.jsoneditsThe project folderplain text: a file per visual, per tablevalidaterendercommitOffline validatornames and schema, in secondsPower BI Desktopapply external changes, lookGita diff a reviewer can readwhat the render showsEvery tool reads the same files. None of them needs the .pbix.
Three readers, one folder. The validator is fast, the render is the truth, Git keeps the history.

Fabric Git integration speaks the same formats. Connect a workspace to a Git repository and Fabric exports its reports as PBIR, with a .platform file per item. It writes no .pbip: open definition.pbir in the report folder instead.

Save a .pbix as a project

  1. Open the .pbix in Power BI Desktop.
  2. Go to File > Save as and pick a short local folder. Desktop cannot save a project directly to OneDrive or SharePoint.
  3. In the file type list, choose the Power BI project type (.pbip), then save.
  4. Desktop writes <name>.pbip, <name>.Report, <name>.SemanticModel and the .gitignore. The title bar now shows that you are working on a project.
  5. Run git init and commit. Desktop writes CRLF line endings, so turn on Git's autocrlf setting to keep diffs clean.
The Save as dialog with the file type set to Power BI project files (*.pbip)
Step 3: the project type in the Save as dialog. The dialog comes from Windows, so it uses the system's language.

The way back is the same menu: open the .pbip and save it as a .pbix. There is no command-line converter: Microsoft documents File > Save as as the only way, in both directions.

If Save as does not offer the project type, look under File > Options and settings > Options > Preview features. On the Desktop build we used in October 2026, the Power BI Project (.pbip) save option still sat there, ticked, with two sub-options: Store semantic model using TMDL format and Store reports using enhanced metadata format (PBIR).

Power BI Desktop Options, Preview features: Power BI Project (.pbip) save option ticked, with Store semantic model using TMDL format and Store reports using enhanced metadata format (PBIR), and Detect and reload external PBIP changes
Options > Preview features on our build: the project format, its TMDL and PBIR sub-options, and the option that reloads external changes.

Pitfalls

One writer at a time. Desktop and an external editor can overwrite each other's changes. Desktop only writes to the files when you save, so save there before you edit them. With the preview option Detect and reload external PBIP changes (August 2026 release or later), Desktop offers to Apply external changes, which discards any unsaved work in Desktop.

Desktop rewrites what it saves. After a save in Desktop, files our scripts had written came back reformatted. Positions gained a digit, and files lost their final line break:

DIFF
-    "y": 358.0851063829787,
+    "y": 358.08510638297872,

Commit that save on its own, so the noise does not hide a real change.

Long paths on Windows. Paths are limited to 260 characters by default, and every visual sits four folders below definition. Long table names count too. Keep the root short: in C:\ColisPro, every visual.json path is 106 characters.

Schema versions. Every JSON file declares its schema in $schema, with the version in the URL. Colis Pro mixes visualContainer 2.13.0 (312 files) and 2.9.0 (35 files) in the same report, and Desktop opens both. When we checked, the 2.13.0 URL returned a 404, so a code editor cannot fetch that schema to check your edits.

Try it

Download Colis Pro, free. Unzip it at the root of C:\ (the zip holds a ColisPro folder), open ColisPro.pbip and click Refresh. Then open the folder in a code editor and read it in this order: pages/pages.json, one page folder, one visual.json, then tables/_Mesures.tmdl. Search the report folder for Points saturés: you land on the three visuals that show it.

If you want this structure without writing it by hand, ChartCraft generates designed Power BI projects as a .pbip folder. Its MCP server lets your agent do it on your own semantic model: the agent sends the declarations from your .tmdl files (names and types, never data) and gets back a report folder to place next to your .SemanticModel.

FAQ

Is PBIP replacing PBIX?

No. PBIX remains a primary format, and Desktop converts between the two in both directions through File > Save as.

Can I edit the files while Desktop is open?

Yes, with the preview option Detect and reload external PBIP changes: save in Desktop first, edit the files, then select Apply external changes.

Do I need Fabric to use a Power BI project?

No. A project is a folder on your disk, any Git host can store it, and Desktop's Publish still uploads it to the Service.

With your consent, we use Google Analytics cookies to measure audience. Cookies needed for sign-in and payment are always on. Learn more