{"id":113433,"date":"2026-09-29T14:53:11","date_gmt":"2026-09-29T14:53:11","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=113433"},"modified":"2026-09-29T14:57:31","modified_gmt":"2026-09-29T14:57:31","slug":"power-bi-project-pbip-format-guide","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/data-analytics\/powerbi\/power-bi-project-pbip-format-guide\/","title":{"rendered":"Power BI Project (PBIP) is now generally available. Here&#8217;s why that matters &#8211; and how it works"},"content":{"rendered":"\n<p>Do you know where you were on June 15, 2023? Do not fret, neither do I. But I do remember a <a href=\"https:\/\/community.fabric.microsoft.com\/blog\/fbc_pbiupdatesblog\/deep-dive-into-power-bi-desktop-developer-mode-preview\/5174712\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>blog post written by Rui Romano<\/strong><\/a> announcing the preview of the <strong><a href=\"https:\/\/learn.microsoft.com\/en-us\/power-bi\/developer\/projects\/projects-overview\" target=\"_blank\" rel=\"noreferrer noopener\">Power BI Project (PBIP)<\/a><\/strong> format back then. <\/p>\n\n\n\n<p>Many in the Power BI world rejoiced at this news. Microsoft was <em>finally<\/em> taking steps toward better practices seen in software development and applying them to the analytics space.<\/p>\n\n\n\n<p>As it happens, June 15, 2023 was just the first step. Since then, PBIP has evolved into the format we have today and I am happy to see that, <strong>as of September 2026, PBIP is generally available as a fully-supported feature for Power BI<\/strong> by Microsoft.<\/p>\n\n\n\n<p>Before I explain the mechanics of PBIP, let me outline the real problems it was designed to solve.<\/p>\n\n\n\n<p><em><strong>TL;DR:<\/strong> A Power BI Project (PBIP) saves a Power BI report and its semantic model as folders of plain-text files instead of one binary <code>.pbix<\/code> file. The model is stored as TMDL and the report as PBIR JSON, so Git can show exactly what changed and teams can review, merge, script and automate their work.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-why-does-the-power-bi-project-pbip-format-exist\">Why does the Power BI Project (PBIP) format exist?<\/h2>\n\n\n\n<p>Have you ever worked in Power BI and someone handed you a new version of a report and you had to figure out what changed? I have. A lot of people in this industry have, too. <\/p>\n\n\n\n<p>The problem is, Power BI history is usually stored in a binary format, <a href=\"https:\/\/learn.microsoft.com\/en-us\/power-bi\/create-reports\/sample-datasets#:~:text=.xlsx%20files%3A-,.pbix%3A,-A%20Power%20BI\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>.pbix<\/strong><\/a> &#8211; and comparing two binary files is <em>not <\/em>easy! In many cases, teams end up opening the reports side-by-side and hunting for differences.<\/p>\n\n\n\n<p>In a way, it&#8217;s like playing the classic game of &#8216;spot-the-difference&#8217; &#8211; except, in our case, it may be five, ten, or even <em>hundreds<\/em> of differences that matter. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-why-comparing-pbix-versions-is-so-hard\">Why comparing <code>.pbix<\/code> versions is so hard<\/h3>\n\n\n\n<p>It&#8217;s not just the counting that&#8217;s difficult &#8211; the real challenge is actually figuring out which differences matter, and which ones might cause issues in production.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"875\" height=\"583\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/09\/image-18.png\" alt=\"A mock Power BI version of spot-the-difference!\" class=\"wp-image-113434\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/09\/image-18.png 875w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/09\/image-18-300x200.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/09\/image-18-768x512.png 768w\" sizes=\"auto, (max-width: 875px) 100vw, 875px\" \/><figcaption class=\"wp-element-caption\"><em>Comparing two Power BI Report files: it&#8217;s like &#8216;spot-the-difference&#8217;, only you don\u2019t know how many changes there are&#8230;<\/em><\/figcaption><\/figure>\n\n\n\n<p>Further, have you ever tried to keep track of different versions of your Power BI files with your folder looking like <em>this<\/em>?:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"875\" height=\"398\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/09\/image-19.png\" alt=\"image showing a messy Power BI downloads folder.\" class=\"wp-image-113435\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/09\/image-19.png 875w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/09\/image-19-300x136.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/09\/image-19-768x349.png 768w\" sizes=\"auto, (max-width: 875px) 100vw, 875px\" \/><figcaption class=\"wp-element-caption\"><em>Does your Power BI downloads folder look like this?<\/em><\/figcaption><\/figure>\n\n\n\n<p>The issue is the same. <strong>A <code>.pbix<\/code> file is binary<\/strong>, which makes it difficult to use a tool like <strong><a href=\"https:\/\/git-scm.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Git<\/a><\/strong> the way we do with plain text files such as <code>.html<\/code>, <code>.js<\/code>, and <code>.md<\/code>. Git is excellent at tracking changes in text, but with <code>.pbix<\/code> we&#8217;re mostly stuck saving duplicate copies of each version. <\/p>\n\n\n\n<p>This, of course, fills up repositories quickly, and removes the benefit of reviewing the actual changes. <em>Quick aside: I do know <strong><a href=\"https:\/\/git-lfs.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Git Large File Storage<\/a><\/strong> is an option, but that\u2019s a bridge too far for many just learning Git<\/em>. <\/p>\n\n\n\n<p>If you&#8217;ve worked in this industry long enough you know, that when a published report starts misbehaving, understanding what changed recently is often one of the best clues to what broke.<\/p>\n\n\n\n<p>That&#8217;s the kind of problem PBIP was designed to solve.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-limits-of-a-single-pbix-file-for-teams\">The limits of a single <code>.pbix<\/code> file for teams<\/h2>\n\n\n\n<p>Comparing versions and bloating a Git repo are just two symptoms of a bigger issue. A <code>.pbix<\/code> file is a fine way to move one report from one person to another, but was never built to support a team working on that report <em>together<\/em> over time. <\/p>\n\n\n\n<p>Reusing a tested table, measure pattern, or report page from one file in another usually means rebuilding it by hand instead of copying it over. <\/p>\n\n\n\n<p>Tools don&#8217;t have it much easier, either. Yes, <strong><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/bi-sql-server\/power-bi-introduction-working-with-power-bi-desktop-part-2\/\" target=\"_blank\" rel=\"noreferrer noopener\">Power BI Desktop<\/a><\/strong> can expose a <a href=\"https:\/\/learn.microsoft.com\/en-us\/power-bi\/connect-data\/service-datasets-understand\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>semantic model<\/strong><\/a> over its local <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/bi-sql-server\/how-to-automate-table-level-refresh-in-power-bi\/#:~:text=The%20XMLA%20connection%20is%20a%20Power%20BI%20connection%20endpoint%2C%20open%20to%20any%20developer%20who%20would%20like%20to%20build%20a%20tool%20and%20connect%20to%20it.\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>XMLA endpoint<\/strong><\/a>, but that still means standing up Desktop and making a live connection just to inspect a model. <\/p>\n\n\n\n<p>Linting, automated checks, and <a href=\"https:\/\/www.red-gate.com\/simple-talk\/ai\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>AI assistance<\/strong><\/a> are all far simpler when the model and report definition are already sitting in plain text &#8211; both on your own machine <em>and<\/em> inside a build pipeline that has no Desktop to connect to.<\/p>\n\n\n\n<p>None of this means Power BI authors were doing something wrong. The file format simply did not expose the pieces that a mature delivery process needs. We&#8217;ve been building reports with an incredibly capable visual tool, living in a low-code world while trying to build practices that are better suited to high-code files.<\/p>\n\n\n\n<p>PBIP has changed this.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-is-a-power-bi-project-pbip\">What is a Power BI Project (PBIP)?<\/h2>\n\n\n\n<p><strong>A Power BI Project is still a Power BI report and semantic model. <\/strong>It&#8217;s not a different product, and you don&#8217;t need to stop using Power BI Desktop. The change is <em>how<\/em> the work is stored on disk.<\/p>\n\n\n\n<p>Instead of keeping the report and model inside one <code>.pbix<\/code> file, Power BI Desktop saves your report and semantic model as separate folders with a small <strong>project entry-point file (<code>.pbip<\/code>)<\/strong> that can be double-clicked to open your report in Power BI Desktop. <\/p>\n\n\n\n<p>The <code>SampleModel<\/code> project I use in my own training sessions looks like this:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >SampleModel\/\n\u251c\u2500\u2500 SampleModel.pbip\n\u251c\u2500\u2500 SampleModel.Report\/\n\u2502   \u251c\u2500\u2500 .pbi\/\n\u2502   \u251c\u2500\u2500 .platform\n\u2502   \u251c\u2500\u2500 definition.pbir\n\u2502   \u251c\u2500\u2500 definition\/\n\u2502   \u2514\u2500\u2500 StaticResources\/\n\u251c\u2500\u2500 SampleModel.SemanticModel\/\n\u2502   \u251c\u2500\u2500 .pbi\/\n\u2502   \u251c\u2500\u2500 .platform\n\u2502   \u251c\u2500\u2500 definition.pbism\n\u2502   \u251c\u2500\u2500 definition\/\n\u2502   \u251c\u2500\u2500 diagramLayout.json\n\u2502   \u251c\u2500\u2500 DAXQueries\/\n\u2502   \u2514\u2500\u2500 TMDLScripts\/\n\u2514\u2500\u2500 .gitignore<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-pbix-vs-pbip-a-quick-summary\">PBIX vs PBIP: a quick summary<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Feature<\/th><th>.pbix<\/th><th>PBIP<\/th><\/tr><\/thead><tbody><tr><td>Storage<\/td><td>One binary file<\/td><td>Folders of plain-text files<\/td><\/tr><tr><td>Git diffs<\/td><td>Whole-file copies only<\/td><td>Line-level changes<\/td><\/tr><tr><td>Semantic model<\/td><td>Inside the binary<\/td><td>TMDL, roughly one file per table<\/td><\/tr><tr><td>Report<\/td><td>Inside the binary<\/td><td>PBIR, separate JSON files for pages and visuals<\/td><\/tr><tr><td>Data<\/td><td>Ships in the file<\/td><td>Stays in the git-ignored <code>.pbi\/cache.abf<\/code><\/td><\/tr><tr><td>Automation and linting<\/td><td>Needs Desktop<\/td><td>Works on plain text, including in build pipelines<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-s-inside-a-pbip-folder-structure\">What&#8217;s inside a PBIP folder structure?<\/h2>\n\n\n\n<p>There are a few details in this structure that will help you understand how it works.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-pbip-entry-point-file\">The .pbip entry-point file<\/h3>\n\n\n\n<p>Firstly, <strong>the <code>.pbip<\/code> file is the project entry point<\/strong>. It points Power BI Desktop at a report folder. When you double-click this file in File Explorer, Desktop reads the artifacts array, follows <code>report.path<\/code>, and opens the <code>SampleModel.Report<\/code> folder:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >{\n  \"$schema\": \"https:\/\/developer.microsoft.com\/json-schemas\/fabric\/pbip\/pbipProperties\/1.0.0\/schema.json\",\n  \"version\": \"1.0\",\n  \"artifacts\": [\n    {\n      \"report\": {\n        \"path\": \"SampleModel.Report\"\n      }\n    }\n  ],\n  \"settings\": {\n    \"enableAutoRecovery\": true\n  }\n}<\/pre><\/div>\n\n\n\n<p>The <code>.Report<\/code> folder holds the report definition, including pages, visuals, bookmarks, themes, and the reference to its semantic model. <\/p>\n\n\n\n<p>Note that if the <code>.pbip<\/code> file is ever missing, you can double-click <code>definition.pbir<\/code> in the <code>.Report<\/code> folder instead, and Power BI Desktop opens the report directly. <\/p>\n\n\n\n<p>It also tells Desktop which semantic model the report should connect to:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >{\n  \"$schema\": \"https:\/\/developer.microsoft.com\/json-schemas\/fabric\/item\/report\/definitionProperties\/2.0.0\/schema.json\",\n  \"version\": \"4.0\",\n  \"datasetReference\": {\n    \"byPath\": {\n      \"path\": \"..\/SampleModel.SemanticModel\"\n    }\n  }\n}<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-thick-reports-vs-thin-reports\">Thick reports vs thin reports<\/h3>\n\n\n\n<p>The <code>definition.pbir<\/code> above points to the local semantic model with a relative path, often called a <strong>thick report<\/strong> because the report and model travel together. <strong><code>byPath<\/code> uses a relative path, not an absolute path, and always forward slashes<\/strong>, even on Windows &#8211; so the reference still works after you clone the repository somewhere else. <\/p>\n\n\n\n<p>When Power BI Desktop opens a thick report, it also opens the referenced semantic model in full edit mode. But what does it look like when the report instead references a semantic model already published to the cloud (sometimes called a <strong>thin report<\/strong>?) <\/p>\n\n\n\n<p>Here is the thin report version of the <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/oracle-databases\/json-for-absolute-beginners-part-1-introduction\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>JSON<\/strong><\/a>:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >{\n  \"$schema\": \"https:\/\/developer.microsoft.com\/json-schemas\/fabric\/item\/report\/definitionProperties\/2.0.0\/schema.json\",\n  \"version\": \"4.0\",\n  \"datasetReference\": {\n    \"byConnection\": {\n      \"connectionString\": \"Data Source=powerbi:\/\/api.powerbi.com\/v1.0\/myorg\/pql-assert-demo;initial catalog=TestingModel;access mode=readonly;integrated security=ClaimsToken;semanticmodelid=xxxxx-ba64-40ab-9c9f-363dbxxxx\"\n    }\n  }\n}<\/pre><\/div>\n\n\n\n<p>Notice that <code>byPath<\/code> becomes <code>byConnection<\/code>, with the traditional connection string to a semantic model stored in the Power BI or <a href=\"https:\/\/learn.microsoft.com\/en-us\/fabric\/fundamentals\/microsoft-fabric-overview\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Microsoft Fabric<\/strong><\/a> service.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-why-pbip-keeps-your-data-out-of-git\">Why PBIP keeps your data <em>out<\/em> of Git<\/h3>\n\n\n\n<p>With <code>byConnection<\/code>, Desktop opens the report connected to the remote semantic model &#8211; but does <em>not<\/em> open that model in edit mode. That boundary lets a report author focus on the report, while a separate team keeps ownership of the model.<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The <code>.SemanticModel<\/code> folder holds the tables, relationships, measures, roles, Power Query expressions, and other model metadata.<br><br><\/li>\n\n\n\n<li>Each item folder, whether <code>.Report<\/code> or <code>.SemanticModel<\/code>, also contains two supporting files:<ul><li><code>.platform<\/code>, which tells Fabric the item\u2019s type, display name, and logical ID for Git sync.<\/li><\/ul><div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li><code>.pbi<\/code>, a folder of cached, machine-specific files such as <code>cache.abf<\/code> and <code>localSettings.json<\/code>. This is the folder your <code>.gitignore<\/code> should exclude if you are using version control.<\/li>\n<\/ul>\n<\/div><\/li>\n<\/ul>\n<\/div>\n\n\n<p>This last point is, in my opinion, the most underrated part of the PBIP format. The <code>cache.abf<\/code> file is where the actual data behind a semantic model is stored, and it lives inside the excluded <code>.pbi<\/code> folder. <\/p>\n\n\n\n<p>That means, when you commit or sync a PBIP project to Git, <strong>the<\/strong> <strong>schema goes up &#8211; but the data does not<\/strong>. <\/p>\n\n\n\n<p>This is a meaningful contrast with a <code>.pbix<\/code> file, where the data ships along with everything else. Here, since the data never enters the repository, cloning a PBIP project doesn&#8217;t freely hand someone a copy of your data. Anyone who pulls the project still needs to refresh it with their own credentials before they see live data.<\/p>\n\n\n\n<section id=\"my-first-block-block_58379e2aa7338bc34f0561c6493e0c6d\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Simple Talk is brought to you by Redgate Software<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Take control of your databases with the trusted Database DevOps solutions provider. Automate with confidence, scale securely, and unlock growth through AI.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/solutions\/overview\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Discover how Redgate can help you: Simple Talk is brought to you by Redgate Software\">Discover how Redgate can help you<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-convert-a-pbix-to-pbip-in-power-bi-desktop\">How to convert a PBIX to PBIP in Power BI Desktop<\/h2>\n\n\n\n<p><strong>Power BI Desktop is the supported way to turn an existing PBIX report into a project.<\/strong> You don&#8217;t need a conversion utility or a special deployment tool.<\/p>\n\n\n\n<p>To save an existing PBIX as a project, open the report in Power BI Desktop and select <strong>File > Save As<\/strong>. Then, select <strong>Power BI project files<\/strong> as the file type:<\/p>\n\n\n<div class=\"block-core-list\">\n<ol class=\"wp-block-list\"><\/ol>\n<\/div>\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"875\" height=\"578\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/09\/image-20.png\" alt=\"image showing how to save a PBIP-format file\" class=\"wp-image-113436\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/09\/image-20.png 875w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/09\/image-20-300x198.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/09\/image-20-768x507.png 768w\" sizes=\"auto, (max-width: 875px) 100vw, 875px\" \/><figcaption class=\"wp-element-caption\"><em>How to save a PBIP-format file<\/em><\/figcaption><\/figure>\n\n\n\n<p><strong>Choose a short local folder path<\/strong> and save it there. <\/p>\n\n\n\n<p><em>Avoid a deeply nested OneDrive folder; PBIP turns one file into several nested folders, and Windows path limits can result in deeply-nested paths failing to save.<\/em><\/p>\n\n\n\n<p>Power BI Desktop creates the aforementioned <code>.pbip<\/code>, <code>.Report<\/code>, and <code>.SemanticModel<\/code> items (if not a thin report). It also creates a default <code>.gitignore<\/code> if\/when one doesn&#8217;t already exist in the target folder (or its parent Git repository).<\/p>\n\n\n\n<p>It\u2019s important at this point to mention that PBIP is actually a format that consists of <em>other<\/em> formats: one for the semantic model and one for the report. Let\u2019s review those two in a little more detail.<\/p>\n\n\n\n<div id=\"callout-block_ae1064904a6599c96e4c6000b99c8829\" class=\"callout alignnone\">\n    <div class=\"child-last:mb-0 child-first:mt-0 bg-gray-50 dark:bg-gray-950 p-4xl my-3xl\">\n\n<p><strong>You may also be interested in:<\/strong><\/p>\n\n\n\n<p><a href=\"https:\/\/www.red-gate.com\/simple-talk\/data-analytics\/13-things-i-wish-i-knew-about-power-query\/\" target=\"_blank\" rel=\"noreferrer noopener\">13 things I wish I knew about Power Query (when I first started)<\/a><\/p>\n\n<\/div>\n<\/div> \n\n\n<h2 class=\"wp-block-heading\" id=\"h-tabular-model-definition-language-tmdl-the-power-bi-semantic-model-in-plain-text\">Tabular Model Definition Language (TMDL): the Power BI semantic model in plain text<\/h2>\n\n\n\n<p>The semantic model is the part of a Power BI solution that describes tables, columns, relationships, measures, calculation groups, roles, cultures, and data-source expressions. <\/p>\n\n\n\n<p>Before <a href=\"https:\/\/www.red-gate.com\/simple-talk\/data-analytics\/power-bi-tmdl-benefits-security-risks-best-practices\/#:~:text=Tabular%20Model%20Definition%20Language%20(TMDL)\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Tabular Model Definition Language (TMDL)<\/strong><\/a>, a PBIP semantic model could be saved as a single <code>model.bim<\/code> file in <a href=\"https:\/\/learn.microsoft.com\/en-us\/analysis-services\/tmsl\/tabular-model-scripting-language-tmsl-reference?view=sql-analysis-services-2025\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Tabular Model Scripting Language (TMSL)<\/strong><\/a>. <\/p>\n\n\n\n<p>TMSL is JSON, which is useful for the <a href=\"https:\/\/learn.microsoft.com\/en-us\/analysis-services\/analysis-services-overview?view=sql-analysis-services-2025\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Analysis Services<\/strong><\/a> engine &#8211; but one large JSON document is<em> not<\/em> friendly for people working together! <\/p>\n\n\n\n<p>In version control, <strong>many small files is better than just one large file<\/strong> as they avoid <strong><a href=\"https:\/\/www.red-gate.com\/simple-talk\/devops\/ci-cd\/git-strategizing-branch-commit-review-merge\/#merge-conflicts:~:text=with%20the%20merge.-,Merge%20Conflicts,-In%20large%20teams\" target=\"_blank\" rel=\"noreferrer noopener\">merge conflicts<\/a><\/strong> &#8211; the tedious process of manually reconciling two people\u2019s changes to the same file. <\/p>\n\n\n\n<p>While <strong>TMDL doesn&#8217;t eliminate merge conflicts outright<\/strong>, splitting a model into per-object files makes them less common.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-tmdl-splits-a-model-into-files\">How TMDL splits a model into files<\/h3>\n\n\n\n<p>The <code>SemanticModel\/definition<\/code> folder in my <code>SampleModel<\/code> project shows this split in practice:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >SampleModel.SemanticModel\/\n\u2514\u2500\u2500 definition\/\n    \u251c\u2500\u2500 database.tmdl\n    \u251c\u2500\u2500 expressions.tmdl\n    \u251c\u2500\u2500 functions.tmdl\n    \u251c\u2500\u2500 model.tmdl\n    \u251c\u2500\u2500 relationships.tmdl\n    \u251c\u2500\u2500 cultures\/\n    \u2502   \u2514\u2500\u2500 en-US.tmdl\n    \u2514\u2500\u2500 tables\/\n        \u251c\u2500\u2500 MarvelFact.tmdl\n        \u251c\u2500\u2500 DateDim.tmdl\n        \u251c\u2500\u2500 AlignmentDim.tmdl\n        \u2514\u2500\u2500 ...<\/pre><\/div>\n\n\n\n<p>Every table that is actually loaded into the model &#8211; <code>MarvelFact<\/code>, <code>DateDim<\/code>, <code>AlignmentDim<\/code>, and so on &#8211; gets its own file under <code>tables\/<\/code>. <\/p>\n\n\n\n<p>Anything that is <em>not<\/em> loaded, however, works differently. The queries shown in italics in the <a href=\"https:\/\/learn.microsoft.com\/en-us\/power-query\/power-query-ui#:~:text=The%20Power%20Query%20editor%20represents%20the%20Power%20Query%20user%20interface.\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Power Query Editor<\/strong><\/a> &#8211; along with parameters and <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/bi-sql-server\/power-bi-introduction-power-query-m-formula-language-in-power-bi-desktop-part-6\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Power Query M<\/strong><\/a> functions &#8211; all live together in a single <code>expressions.tmdl<\/code> file instead of getting their own table file. <\/p>\n\n\n\n<p>Additionally, relationships have their own <code>relationships.tmdl<\/code>, and each supported culture gets a file under <code>cultures\/<\/code>. The pattern holds throughout: whatever you can see and edit as a distinct object in Power BI Desktop, TMDL often gives it a distinct place in a file. <\/p>\n\n\n\n<p><em>Note that <code>functions.tmdl<\/code> is where <strong><a href=\"https:\/\/learn.microsoft.com\/en-us\/dax\/best-practices\/dax-user-defined-functions\" target=\"_blank\" rel=\"noreferrer noopener\">user-defined DAX functions<\/a><\/strong> are stored.<\/em><\/p>\n\n\n\n<p>Here&#8217;s an example. The <code>MarvelFact<\/code> table in my <code>SampleModel<\/code> project has a measure defined like this:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >table MarvelFact\n    lineageTag: 743cae73-8a6b-4e03-af99-d194d47d16af\n\n    measure 'Number of Characters' = ```\n            COUNTROWS('MarvelFact')\n            ```\n        formatString: 0\n        lineageTag: df54ac01-57f7-4a28-a338-2609c59c6503\n\n    column ID\n        dataType: int64\n        formatString: 0\n        lineageTag: b7ff624e-3f51-4955-89fb-6129a2da0110\n        summarizeBy: count\n        sourceColumn: ID<\/pre><\/div>\n\n\n\n<p>That is a meaningful improvement over finding the same measure inside a large JSON file. An author can open the table file, review the measure, and see its format string and metadata in context. A code editor like <a href=\"https:\/\/code.visualstudio.com\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Visual Studio Code (VSC, or VS Code)<\/strong><\/a> can then provide syntax highlighting and language support. <\/p>\n\n\n\n<p>A linter, meanwhile, can inspect naming conventions (e.g., make sure measures and tables have spaces between words and proper casing) &#8211; and a script can add a standard measure to every model that needs it. The fact that this is now in a standard, plain-text format opens up a lot of possibilities. <\/p>\n\n\n\n<p>I will also note a happy accident of PBIP: <strong>AI tools, like <a href=\"https:\/\/github.com\/features\/copilot\" target=\"_blank\" rel=\"noreferrer noopener\">GitHub Copilot<\/a>, are <em>really<\/em> good at making updates to Power BI<\/strong>. I recently wrote an article about this <a href=\"https:\/\/www.red-gate.com\/simple-talk\/data-analytics\/powerbi\/how-to-use-the-power-bi-desktop-bridge-to-automate-tasks-in-power-bi-desktop-and-github-copilot\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>here on Simple Talk<\/strong><\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-pbir-the-power-bi-report-as-json-files\">PBIR: the Power BI report as JSON files<\/h2>\n\n\n\n<p>The report also has a history. Older PBIP reports used a single <code>report.json<\/code> file. That file represents the report, but it has the same co-development problem as one large model file. A small visual formatting change and a new page can both land in the same huge JSON document.<\/p>\n\n\n\n<p><strong>The new Power BI Report format (PBIR) stores the report in a definition folder <\/strong>instead &#8211; with pages, visuals, and bookmarks separated into their own JSON files. <\/p>\n\n\n\n<p>A typical report definition looks like this:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >SampleModel.Report\/\n\u251c\u2500\u2500 definition.pbir\n\u2514\u2500\u2500 definition\/\n    \u251c\u2500\u2500 pages\/\n    \u2502   \u251c\u2500\u2500 ReportSection\/\n    \u2502   \u2502   \u251c\u2500\u2500 page.json\n    \u2502   \u2502   \u2514\u2500\u2500 visuals\/\n    \u2502   \u251c\u2500\u2500 f2054b40c53166dc4484\/\n    \u2502   \u2502   \u251c\u2500\u2500 page.json\n    \u2502   \u2502   \u2514\u2500\u2500 visuals\/\n    \u2502   \u251c\u2500\u2500 941d59188f8e383e90d8\/\n    \u2502   \u251c\u2500\u2500 cdebc12e8cab59f864b6\/\n    \u2502   \u2514\u2500\u2500 pages.json\n    \u251c\u2500\u2500 report.json\n    \u2514\u2500\u2500 version.json<\/pre><\/div>\n\n\n\n<p>Notice that <strong>Power BI Desktop names each page folder after its logical ID rather than its display name<\/strong> (apart from the very first page.) It&#8217;s small detail, but one that matters if you&#8217;re looking for a specific page by name in the file system. <\/p>\n\n\n\n<p>The <code>page.json<\/code> inside each folder is where the display name actually lives, and <code>pages.json<\/code> tracks the page order and which page is active (<code>activePageName<\/code>):<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >{\n  \"$schema\": \"https:\/\/developer.microsoft.com\/json-schemas\/fabric\/item\/report\/definition\/pagesMetadata\/1.1.0\/schema.json\",\n  \"pageOrder\": [\n    \"ReportSection\",\n    \"f2054b40c53166dc4484\",\n    \"941d59188f8e383e90d8\",\n    \"cdebc12e8cab59f864b6\"\n  ],\n  \"activePageName\": \"ReportSection\"\n}<\/pre><\/div>\n\n\n\n<p>The benefits of PBIR extend well beyond source control. A team with a standard page layout can copy a page folder into a new report and make targeted modifications rather than rebuilding from scratch. Teams can also inspect a visual&#8217;s JSON definition to understand the filters, queries, formatting, and positioning generated by Power BI. <\/p>\n\n\n\n<p>Additionally, since report elements are stored in a structured format, <strong>scripts can automate bulk changes across reports<\/strong> &#8211; such as updating a property in every <code>visual.json<\/code> file. <\/p>\n\n\n\n<p>We can even analyze report definitions to enforce standards. This includes accessibility checks for alt text, color contrast, and other usability requirements.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-security-note-sensitive-values-in-pbir-filters\">Security note: sensitive values in PBIR filters<\/h3>\n\n\n\n<p>One additional consideration is that PBIR stores certain report settings and filter values in plain-text JSON files. If a report contains <strong>hard-coded filter values that include sensitive information, those values may be visible within the project files<\/strong>. <\/p>\n\n\n\n<p>While this behavior is necessary to support transparency and source control, it reinforces the need for good development practices. <strong>Teams should avoid embedding sensitive data in report filters<\/strong> whenever possible, and ensure their repositories, access controls, and governance processes are managed appropriately.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-where-to-start-with-pbip\">Where to start with PBIP<\/h2>\n\n\n\n<p>Okay, you may be reading this at this point and think, <em>\u201cOh man, I need to learn Git, where do I even start with PBIP for all the reports I have?\u201d<\/em> <\/p>\n\n\n\n<p>Just know this: <strong>you do <em>not<\/em> need to convert every report in your tenant tomorrow!<\/strong><\/p>\n\n\n\n<p>Start with a report that has active development, a clear owner, and a reason to improve its delivery process. Save it as PBIP, and set up version control with Git.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-conclusion-treat-power-bi-reports-like-software\">Conclusion: treat Power BI reports like software<\/h2>\n\n\n\n<p>The general availability of Power BI Project format is significant because it recognizes what many Power BI teams have understood for years: <strong>reports and semantic models are software artifacts<\/strong>. <\/p>\n\n\n\n<p>They contain logic, configuration, dependencies, and business rules that deserve the same level of governance, testing, and lifecycle management as any other production system.<\/p>\n\n\n\n<p>PBIP brings a project-based structure to Power BI Desktop. TMDL provides a human-readable representation of semantic models, and PBIR introduces a structured format for reports. <\/p>\n\n\n\n<p>Together, these innovations make <strong>version control systems like Git genuinely useful for Power BI development<\/strong>. <\/p>\n\n\n\n<p>More importantly, PBIP creates a stronger foundation for transparency, collaboration, automation, and shared ownership. Power BI teams now have a much better way to develop, review, test, and maintain reports and semantic models as a team.<\/p>\n\n\n\n<section id=\"my-first-block-block_38b51351980133a4e11f3a2fb36afc71\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Subscribe to the Simple Talk newsletter<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            All the latest articles, guides, podcasts, and more &#8211; delivered straight to your inbox.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/simple-talk\/subscribe\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Subscribe now: Subscribe to the Simple Talk newsletter\">Subscribe now<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What is a Power BI Project (PBIP)?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">A Power BI Project (PBIP) is a folder-based way of saving a Power BI report and semantic model as plain-text files rather than one <code>.pbix<\/code> file. A small <code>.pbip<\/code> file opens the project in Power BI Desktop, a <code>.Report<\/code> folder holds the report definition, and a <code>.SemanticModel<\/code> folder holds the model. It&#8217;s the same report and model, just stored differently.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. What&#039;s the difference between PBIX and PBIP?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">A <code>.pbix<\/code> is a single binary file that bundles the report, model and data. A PBIP splits the report and semantic model into separate folders of text files, with the model in TMDL and the report in PBIR JSON. That lets Git show line-by-line changes and keeps cached data out of the repository via the <code dir=\"ltr\">.pbi<\/code> folder.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. Can you use Git with Power BI?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">Yes, but Git works best with PBIP. A <code>.pbix<\/code> is binary, so Git can only store whole copies and can&#8217;t show what changed. Save the report as a Power BI Project and Git can track changes to individual tables, measures, pages and visuals as text. Desktop also creates a default <code dir=\"ltr\">.gitignore<\/code> that excludes machine-specific files.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. How do I convert a PBIX to PBIP?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">Open the report in Power BI Desktop, select File &gt; Save As, and choose Power BI project files as the file type. Save to a short local folder path, because PBIP creates nested folders and long paths, such as deeply nested OneDrive folders, can fail to save. Desktop creates the <code>.pbip<\/code>, <code>.Report<\/code>, and <code>.SemanticModel<\/code> items.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">5. What are TMDL and PBIR?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">TMDL (Tabular Model Definition Language) stores a semantic model as plain-text files, roughly one per table. PBIR (Power BI Report format) stores a report as separate JSON files for pages, visuals and bookmarks. Together they replace the older single <code>model.bim<\/code> and <code>report.json<\/code> files, making merge conflicts less common &#8211; and letting scripts and linters work on the definitions.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>PBIP is Power BI&#8217;s generally available project format. Learn how it stores reports as TMDL and PBIR files, works with Git, and how to convert a PBIX.&hellip;<\/p>\n","protected":false},"author":344919,"featured_media":107492,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":true,"footnotes":""},"categories":[159160,53,159107,159166],"tags":[101611],"coauthors":[159224],"class_list":["post-113433","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-data-analytics","category-featured","category-news","category-powerbi","tag-power-bi"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/113433","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/344919"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=113433"}],"version-history":[{"count":11,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/113433\/revisions"}],"predecessor-version":[{"id":113450,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/113433\/revisions\/113450"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/107492"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=113433"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=113433"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=113433"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=113433"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}