<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Riccardo Gregori</title>
    <description>The latest articles on Forem by Riccardo Gregori (@_neronotte).</description>
    <link>https://forem.com/_neronotte</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1533094%2Fe0d7f2bb-ed60-4921-9b81-254de405c0c4.png</url>
      <title>Forem: Riccardo Gregori</title>
      <link>https://forem.com/_neronotte</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/_neronotte"/>
    <language>en</language>
    <item>
      <title>Power Pages SPA Learning By Doing - Keep it clean</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Sun, 22 Mar 2026 15:09:23 +0000</pubDate>
      <link>https://forem.com/_neronotte/power-pages-spa-learning-by-doing-keep-it-clean-mg4</link>
      <guid>https://forem.com/_neronotte/power-pages-spa-learning-by-doing-keep-it-clean-mg4</guid>
      <description>&lt;p&gt;Lately, I’ve been working a lot with &lt;a href="https://learn.microsoft.com/en-us/power-pages/configure/create-code-sites" rel="noopener noreferrer"&gt;&lt;strong&gt;Power Pages Single Page Applications (SPA)&lt;/strong&gt;&lt;/a&gt;.&lt;br&gt;
I find this approach &lt;strong&gt;far more effective&lt;/strong&gt; than the previous one, and its &lt;strong&gt;potential&lt;/strong&gt;, combined with &lt;strong&gt;GitHub Copilot’s ability to generate React code&lt;/strong&gt;, makes portal development &lt;strong&gt;faster&lt;/strong&gt;, &lt;strong&gt;more efficient&lt;/strong&gt;, and honestly even &lt;strong&gt;fun&lt;/strong&gt; 😎.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Power Pages SPA&lt;/strong&gt; reached General Availability at the beginning of &lt;strong&gt;February&lt;/strong&gt;, almost &lt;strong&gt;two months ago&lt;/strong&gt;, but even today the &lt;strong&gt;official documentation&lt;/strong&gt; is still not fully exhaustive. Many things can only be discovered by &lt;strong&gt;hands-on experimentation&lt;/strong&gt; with the platform.&lt;/p&gt;

&lt;p&gt;This article — or rather, this &lt;strong&gt;series of articles&lt;/strong&gt; — is born with the goal of sharing the &lt;strong&gt;pain points&lt;/strong&gt; and &lt;strong&gt;hidden gems&lt;/strong&gt; I’ve encountered during my personal &lt;strong&gt;journey&lt;/strong&gt; exploring this new technology.&lt;/p&gt;

&lt;p&gt;So… what are we waiting for?  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let’s get started! 🚀&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🤔 The issue - Stale files generated by chunking strategy
&lt;/h2&gt;

&lt;p&gt;If you build a Power Pages SPA using &lt;strong&gt;React+Vite&lt;/strong&gt;, as in &lt;a href="https://github.com/microsoft/power-pages-samples/tree/main/samples/bring-your-own-code/react/car-sales-website" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt;, every time you build your portal via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build process automatically splits your site source code in a set of chunked files&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vp1hrjh6azcn7814fin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vp1hrjh6azcn7814fin.png" alt="Chunked files" width="555" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;random-looking hashes&lt;/strong&gt; in chunk names change because Vite uses content hashes by default. This means that when you change the content of your site, each chunk content may change, thus the chunk name changes too.&lt;/p&gt;

&lt;p&gt;Those chunks become &lt;strong&gt;Web Files&lt;/strong&gt; records in dataverse, that are &lt;strong&gt;created&lt;/strong&gt; or &lt;strong&gt;updated&lt;/strong&gt; when you type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;upload-code-site&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--rootPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exactly... that command, by itself, &lt;strong&gt;adds&lt;/strong&gt; or &lt;strong&gt;updates&lt;/strong&gt; Web Files, but &lt;strong&gt;does not remove old, now unused, files&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On the medium-long run, this is a &lt;strong&gt;real issue, because your site definition will be crowded by tens or thousands of useless Web Files&lt;/strong&gt;, that you will deploy between environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 How to fix it?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  📌 Step A. Apply deterministic names on chunks
&lt;/h3&gt;

&lt;p&gt;To avoid generate random-looking hashed names, you can customize how Vite produces the output files, with different possible strategies:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important notice&lt;/strong&gt;: these strategies are tested with &lt;strong&gt;Vite 7.3.1&lt;/strong&gt;. &lt;strong&gt;Vite 8&lt;/strong&gt; (released March 2026) may require a different approach.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  1. Disable hashing entirely (simplest)
&lt;/h4&gt;

&lt;p&gt;In &lt;code&gt;vite.config.ts&lt;/code&gt;, override the &lt;code&gt;rollupOptions.output&lt;/code&gt; naming:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rollupOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Static names — no hashes at all&lt;/span&gt;
        &lt;span class="na"&gt;entryFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;chunkFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;assetFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].[ext]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you fully predictable names like &lt;code&gt;index.js&lt;/code&gt;, &lt;code&gt;vendor.js&lt;/code&gt;, etc. The downside is no &lt;strong&gt;cache-busting&lt;/strong&gt;, but for Power Pages/ALM this is often exactly what you want.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Use a fixed/stable hash (content-based, but stable)
&lt;/h4&gt;

&lt;p&gt;If you want hashes but want them to only change when the &lt;strong&gt;content&lt;/strong&gt; actually changes (which is actually Vite's intent, but chunk splitting can cause cascading renames):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rollupOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;entryFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].[hash].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;chunkFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].[hash].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;assetFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].[hash].[ext]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The issue here is usually not the hash algorithm but &lt;strong&gt;chunk splitting&lt;/strong&gt; — if Rollup splits chunks differently between builds, names drift. Fix that with option 3.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Pin your manual chunks (most robust for ALM)
&lt;/h4&gt;

&lt;p&gt;The real culprit is often Rollup's &lt;strong&gt;automatic code splitting&lt;/strong&gt; producing differently-named dynamic chunks. Lock it down with &lt;code&gt;manualChunks&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rollupOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;entryFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;chunkFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;assetFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].[ext]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;manualChunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// Pin vendor libs into a stable named chunk&lt;/span&gt;
          &lt;span class="na"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="c1"&gt;// Add other large deps as needed&lt;/span&gt;
          &lt;span class="c1"&gt;// router: ['react-router-dom'],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way Rollup won't invent new chunk names — you've explicitly defined the boundaries.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Disable code splitting entirely (nuclear option)
&lt;/h4&gt;

&lt;p&gt;If the &lt;strong&gt;SPA is small enough&lt;/strong&gt;, just bundle everything into one file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rollupOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;inlineDynamicImports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// single bundle&lt;/span&gt;
        &lt;span class="na"&gt;entryFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/index.js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;assetFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].[ext]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Recommended approach for Power Pages
&lt;/h4&gt;

&lt;p&gt;In the end I've decided to combine options 1+3, that work for my needs. &lt;br&gt;
Considering that I'm using the following libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fluentui&lt;/code&gt;: for ui components of my website&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;react-router-dom&lt;/code&gt;: to handle url routing in my SPA&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;powerbi-client-react&lt;/code&gt;: to embed powerbi reports on the site&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;react-google-charts&lt;/code&gt;: for home page charts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;adal-angular&lt;/code&gt;: to manage authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is (part of) my &lt;code&gt;vite.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// absolute path - Power Pages handles routing&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;rollupOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;entryFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;chunkFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].js`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;assetFileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`assets/[name].[ext]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;manualChunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;fluent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fluentui/react-components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fluentui/react-icons&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;powerbi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;powerbi-client-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;charts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-google-charts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;adal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;adal-angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives me &lt;strong&gt;a fixed, known set of filenames&lt;/strong&gt; that map 1:1 to my Power Pages Web Files, and they only change if you explicitly add a new &lt;code&gt;manualChunks&lt;/code&gt; group.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7hfu4nrl3fo9urfn6xvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7hfu4nrl3fo9urfn6xvb.png" alt="Generated chunks" width="623" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🧹 B. Good, but now how I get rid of the old files?
&lt;/h3&gt;

&lt;p&gt;A small hint on how to fix it is given by &lt;code&gt;pac pages upload-code-site&lt;/code&gt; command itself. When you run it, it says:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjo1172oawfylx3ldqpvl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjo1172oawfylx3ldqpvl.png" alt="Power pages bundles" width="800" height="293"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Found 10 bundle pattern(s) to match for cleanup:
1. main .*. js
3. vendor .*. js
4. vendor .*. css
5. chunk -*. js
6. chunk -*. css
7. bundle .*. js
8. bundle .*. css
9. index -*. js
10. index -*. css

Note: You can customize these patterns by adding 'bundleFilePatterns' array in 'powerpages.config.json' file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, even if isn't documented anywhere, it seems it tries to clean up the mess before pushing, and it seems we can tweak the &lt;code&gt;powerpages.config.json&lt;/code&gt; file to customize it's behavior. After a few tests, I've found that if you add a &lt;code&gt;bundleFilePatterns&lt;/code&gt; node with an &lt;strong&gt;array of file patterns&lt;/strong&gt;, those are "pruned" by &lt;code&gt;pac pages upload-code-site&lt;/code&gt; before being replaced by the new bundles generated from your build.&lt;/p&gt;

&lt;p&gt;In my case, the configuration that works is the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"siteName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-site-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"defaultLandingPage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compiledPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bundleFilePatterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"main.*.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"vendor.*.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"vendor.*.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"chunk-*.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"chunk-*.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"bundle.*.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"bundle.*.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"index-*.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"index-*.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"index.*.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"index.*.css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Filled.*.woff2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Filled.*.woff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Filled.*.ttf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Light.*.woff2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Light.*.woff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Light.*.ttf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Regular.*.woff2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Regular.*.woff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Regular.*.ttf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Resizable.*.woff2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Resizable.*.woff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"FluentSystemIcons-Resizable.*.ttf"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it works... it effectively removes all stale files from your local &lt;code&gt;.powerpages-site&lt;/code&gt; folder, and when pushed to Dataverse, those file are removed from there as well... or at least, 90% of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  🖖🏼 C. Let's finish by hand
&lt;/h3&gt;

&lt;p&gt;Even after tweaking the &lt;code&gt;vite.config.ts&lt;/code&gt; and the &lt;code&gt;powerpages.config.json&lt;/code&gt;, I've found a few files that still remain on my Dataverse environment. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6oxcfhv0bqpz6mmvj5li.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6oxcfhv0bqpz6mmvj5li.png" alt="Stale files on Dataverse" width="353" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Frankly I think it's a bug of PAC CLI. The only solution I've found is to remove them manually, hoping they won't be re-generated again thanks to &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Please note&lt;/strong&gt;: when you do it, you may occur in the following &lt;strong&gt;non-blocking-error&lt;/strong&gt; while running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XRM Network error: An error occurred in the PowerPageComponentDeletePlugin.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only solution I've found to solve this issue is to &lt;strong&gt;edit manually the file&lt;/strong&gt; called &lt;code&gt;.portalconfig/my-site-url-manifest.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use &lt;strong&gt;Plugin Trace Viewer&lt;/strong&gt; XrmToolBox Plugin to get all exceptions coming from plugin &lt;code&gt;Microsoft.PowerPages.Core.Plugins.PowerPageComponentDeletePlugin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;from the trace message, extract the GUID of the missing component.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rap14jamy8hwnlwuwg6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rap14jamy8hwnlwuwg6.png" alt="Exception" width="800" height="83"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;search for that GUID into file &lt;code&gt;.portalconfig/my-site-url-manifest.yml&lt;/code&gt; and remove the yaml section referring to it. It should be similar to the following.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# sample block to remove&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;RecordId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;f9d754fb-4b71-4491-bad7-7c06eb21bce5&lt;/span&gt;
  &lt;span class="na"&gt;DisplayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;index.COcDBgFa.css&lt;/span&gt;
  &lt;span class="na"&gt;CheckSum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fa7ab4d0d9716e5ae809c29050b94cdaf004c4c51e1f35241716333b1352fb4a&lt;/span&gt;
  &lt;span class="na"&gt;IsDeleted&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may also want to find all lines with &lt;code&gt;IsDeleted: true&lt;/code&gt; and &lt;strong&gt;remove them&lt;/strong&gt;, and also &lt;strong&gt;clear the contents&lt;/strong&gt; of the &lt;code&gt;.portalconfig/manifest.yml&lt;/code&gt; file (that contains only references to deleted files).&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Final considerations
&lt;/h2&gt;

&lt;p&gt;Power Pages SPA is a &lt;strong&gt;huge step forward&lt;/strong&gt; compared to the classic portal model, especially if you come from a modern frontend background. Being able to bring your own React stack, use Vite, and rely on tools like GitHub Copilot dramatically changes the &lt;strong&gt;developer experience&lt;/strong&gt; and the &lt;strong&gt;delivery speed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That said, today’s reality is that &lt;strong&gt;Power Pages SPA is still a young product&lt;/strong&gt;. Some important behaviors — like how bundles are handled, how cleanup works, and how Dataverse Web Files are managed over time — are either &lt;strong&gt;underdocumented&lt;/strong&gt; or &lt;strong&gt;not documented at all&lt;/strong&gt;. As a result, production-ready solutions still require a fair amount of &lt;strong&gt;experimentation, reverse‑engineering&lt;/strong&gt;, and sometimes even &lt;strong&gt;manual intervention&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The chunking issue described in this article is a perfect example:&lt;br&gt;
nothing is &lt;em&gt;technically broken&lt;/em&gt;, but without deterministic filenames and explicit cleanup rules, you end up with a &lt;strong&gt;slowly degrading site definition&lt;/strong&gt;, polluted by stale Web Files that get deployed across environments and make ALM harder than it should be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My personal takeaway&lt;/strong&gt; so far is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Treat the build output as a contract&lt;/strong&gt;: Stable filenames and pinned chunks are not optional in enterprise scenarios — they’re a prerequisite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assume PAC CLI cleanup is best‑effort, not authoritative&lt;/strong&gt;: &lt;code&gt;bundleFilePatterns&lt;/code&gt; helps a lot, but today it doesn’t guarantee a 100% clean state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expect to occasionally "drop below the abstraction"&lt;/strong&gt;: Editing manifests and cleaning Dataverse artifacts by hand shouldn’t be necessary — but right now, it sometimes is.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Despite all of this, &lt;strong&gt;I still strongly believe Power Pages SPA is the right direction&lt;/strong&gt;. The flexibility it gives you largely outweighs the current rough edges, especially if you’re already comfortable with React, modern bundlers, and CI/CD pipelines.&lt;/p&gt;

&lt;p&gt;Hopefully, future iterations of the platform — and better documentation — will make many of the workarounds shown here obsolete. Until then, &lt;strong&gt;knowing where the sharp edges are&lt;/strong&gt; is the key to using this technology effectively.&lt;/p&gt;

&lt;p&gt;In the next articles of this series, I’ll dive into other &lt;strong&gt;real-world issues and patterns&lt;/strong&gt; I’ve encountered while building Power Pages SPAs — from authentication quirks, to CSP constraints, to deployment strategies that actually scale.&lt;/p&gt;

&lt;p&gt;Stay tuned 🚀&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>powerapps</category>
      <category>powerfuldevs</category>
      <category>powerpages</category>
    </item>
    <item>
      <title>Simple, but yet rock hard, truth.</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Mon, 02 Mar 2026 22:20:43 +0000</pubDate>
      <link>https://forem.com/_neronotte/simple-but-yet-rock-hard-truth-25la</link>
      <guid>https://forem.com/_neronotte/simple-but-yet-rock-hard-truth-25la</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/the_nortern_dev" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3630167%2F2e206d7e-04d3-484b-8a73-1f98d17a0e1a.png" alt="the_nortern_dev"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/the_nortern_dev/the-hardest-part-of-being-a-developer-isnt-coding-its-disappearing-quietly-52l" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;The Hardest Part of Being a Developer Isn’t Coding. It’s Disappearing Quietly.&lt;/h2&gt;
      &lt;h3&gt;NorthernDev ・ Feb 28&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#discuss&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#mentalhealth&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#career&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>discuss</category>
      <category>mentalhealth</category>
      <category>webdev</category>
      <category>career</category>
    </item>
    <item>
      <title>Greg.KeySub – The Ultimate Revenge Against the Italian Keyboard</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Mon, 09 Feb 2026 21:42:04 +0000</pubDate>
      <link>https://forem.com/_neronotte/gregkeysub-the-ultimate-revenge-against-the-italian-keyboard-2ena</link>
      <guid>https://forem.com/_neronotte/gregkeysub-the-ultimate-revenge-against-the-italian-keyboard-2ena</guid>
      <description>&lt;p&gt;There’s a group of people who have been silently suffering for years.&lt;br&gt;
A forgotten minority, ignored by institutions, abandoned by the tech industry, left behind even by Microsoft, Apple, and the United Nations.&lt;/p&gt;

&lt;p&gt;I’m talking about us: &lt;strong&gt;Italian developers forced to use the Italian keyboard layout&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Yes, &lt;strong&gt;that one&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The one that gives you the incredibly useful “§” key, which you have never pressed voluntarily in your entire life, but denies you the most fundamental character of the modern software era:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The backtick: `&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The backbone of Markdown.&lt;br&gt;
The unsung hero of code snippets.&lt;br&gt;
The vital organ of shell commands.&lt;br&gt;
A character so essential that GitHub should send you a condolence letter every time you try to write documentation with an Italian keyboard.&lt;/p&gt;

&lt;p&gt;The Italian layout? It simply forgot it. Oh, but it has plenty of "useful" keys such as "§" or "ç"... &lt;/p&gt;

&lt;p&gt;Every Italian developer has faced at least one of the following situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Typing ALT+96 with the elegance of a musician playing the piano with their elbows.&lt;/li&gt;
&lt;li&gt;Juggling with WIN+. &amp;gt; move to chars &amp;gt; select the `&lt;/li&gt;
&lt;li&gt;Googling “backtick” just to copy and paste the damn symbol.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So… I decided to stop suffering.&lt;/p&gt;

&lt;p&gt;I did what any developer does when reality refuses to cooperate:&lt;br&gt;
&lt;strong&gt;I wrote a tool to bypass reality entirely&lt;/strong&gt;. Or better, I &lt;strong&gt;vibe coded&lt;/strong&gt; a tool to save me hours per week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing: Greg.KeySub
&lt;/h2&gt;

&lt;p&gt;Available here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/neronotte/Greg.KeySub" rel="noopener noreferrer"&gt;https://github.com/neronotte/Greg.KeySub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Greg.KeySub&lt;/strong&gt; is a tiny Windows tool that quietly lives in your system tray — right next to a few utilities, five unnecessary Electron apps, and three icons whose meaning you’ve forgotten years ago.&lt;/p&gt;

&lt;p&gt;Its one and only job?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listen to the keys you press&lt;/strong&gt;, and whenever you hit that useless §, it magically transforms it into a glorious, essential, life-improving `.&lt;/p&gt;

&lt;p&gt;Yes, you read that right: &lt;strong&gt;§ becomes `&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Because that key had no purpose. None.&lt;br&gt;
Now, finally, it serves a higher mission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why You Should Install It Right Now
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;🎯 Because it saves time.&lt;/strong&gt;&lt;br&gt;
Write Markdown like a normal person, not like someone performing finger acrobatics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔥 Because it gives purpose to a key that had none.&lt;/strong&gt;&lt;br&gt;
Digital recycling at its finest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;😤 Because the Italian keyboard layout will never fix itself.&lt;/strong&gt;&lt;br&gt;
Let’s be honest. It’s been decades. It’s not happening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⭐ Because it’s open source.&lt;/strong&gt;&lt;br&gt;
You can contribute, improve it, fork it, or just stare at the code thinking “I would’ve done it differently.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Install it, try it, love it ❤️
&lt;/h2&gt;

&lt;p&gt;Find it here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/neronotte/Greg.KeySub" rel="noopener noreferrer"&gt;https://github.com/neronotte/Greg.KeySub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re an Italian programmer who writes Markdown, this tool is like that 4 PM coffee: &lt;strong&gt;You didn’t know you needed it until you started using it&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>programming</category>
      <category>showdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Modern Power App development takes a lot of... SPACE.</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Tue, 20 Jan 2026 22:03:37 +0000</pubDate>
      <link>https://forem.com/_neronotte/modern-power-app-development-takes-a-lot-of-space-3f3j</link>
      <guid>https://forem.com/_neronotte/modern-power-app-development-takes-a-lot-of-space-3f3j</guid>
      <description>&lt;h2&gt;
  
  
  🧠 The Paradigm Shift: Why We Don’t Argue UI Anymore
&lt;/h2&gt;

&lt;p&gt;There was a time (you remember it) when any client request that drifted outside the "blessed" Power Apps patterns triggered a &lt;strong&gt;long conversation about platform constraints&lt;/strong&gt;. We tried to reshape requirements, push standard controls, and hope stakeholders would accept a slightly clunky interaction with the UI in the name of long-term maintainability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That era is over&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;AI-assisted development&lt;/strong&gt; (hello my dear friend, GitHub Copilot), it’s now faster and safer to build &lt;em&gt;small&lt;/em&gt;, &lt;em&gt;targeted&lt;/em&gt; customizations—&lt;em&gt;PCFs or React WebResources&lt;/em&gt;—than to litigate UX. &lt;/p&gt;

&lt;p&gt;You can ship exactly what the client envisions, document it properly, and make it maintainable long-term. The payoff is huge: &lt;strong&gt;better UX → higher adoption → stronger platform stickiness&lt;/strong&gt;. In other words, great UI is not a "nice-to-have"—it’s a retention strategy.&lt;/p&gt;

&lt;p&gt;When you replace “discuss why we can’t” with “deliver what delights,” you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Earn trust faster&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accelerate sign-off&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anchor the customer to the platform&lt;/strong&gt; with experiences they love&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But there’s a catch…&lt;/p&gt;

&lt;h2&gt;
  
  
  🧱 The Problem: A Dozen PCFs/WebResources = Disk Space Meltdown
&lt;/h2&gt;

&lt;p&gt;React-based PCFs and WebResources lean on Node’s ecosystem. And Node’s ecosystem leans on… &lt;code&gt;node_modules&lt;/code&gt;—the folder that eats hard drives for breakfast.&lt;/p&gt;

&lt;p&gt;Every project carries its own dependency tree. Multiply that across 8, 12, 20 PCFs/WebResources… and your laptop screams. It’s common to see hundreds of thousands of files and tens (or hundreds) of GB consumed across projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good news&lt;/strong&gt;: there’s a clean, robust solution that many Power Platform devs underuse:&lt;/p&gt;

&lt;h2&gt;
  
  
  🧩 The Hidden Gem: NPM Workspaces
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;NPM Workspaces&lt;/strong&gt; let you manage multiple packages/projects (PCFs/WebResources) under one repository, sharing a single top-level node_modules whenever possible. This dramatically reduces duplication and makes dependency management more predictable and CI-friendly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.npmjs.com/cli/v7/using-npm/workspaces" rel="noopener noreferrer"&gt;Official docs: NPM Workspaces&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;One central &lt;code&gt;node_modules&lt;/code&gt;&lt;/strong&gt; (plus small per-package shims when needed)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Shared tooling&lt;/strong&gt; (TypeScript config, lint rules, build scripts)&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Shared local packages&lt;/strong&gt; (@types/xrm, @fluentui/react-components) used across PCFs/WRs via workspace:&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Unified scripts&lt;/strong&gt; (build, test, lint) across all packages&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Faster CI&lt;/strong&gt; with a single dependency tree and cache&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🤔 How Does It Works?
&lt;/h2&gt;

&lt;p&gt;It's quite straightforward. All you need is to define a &lt;code&gt;package.json&lt;/code&gt; and a &lt;code&gt;package-lock.json&lt;/code&gt; files at the root of your project folder, just above the folders containing PCFs and WebResources. This will make your project folder a &lt;strong&gt;monorepo&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;📂 my-monorepo/
├─ 📄 package.json         &amp;lt;-- workspace root
├─ 📄 package-lock.json
│
├─ 📂 Pcf/
│  ├─ 📂 pcf1/
│  │  ├─ 📄 package.json
│  │  └─ ...
│  └─ 📂 pcf2/
│     ├─ 📄 package.json
│     └─ ...
│
└─ 📂 WebResources/
   ├─ 📂 webresource1/
   │  ├─ 📄 package.json
   │  └─ ...
   └─ 📂 webresource2/
      ├─ 📄 package.json
      └─ ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is important that the root &lt;code&gt;package.json&lt;/code&gt; file contains the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-monorepo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workspaces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"Pcf/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"WebResources/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can do it manually, or via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-monorepo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-y&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;private&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;workspaces&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Pcf/*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;workspaces&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"WebResources/*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can create your PCFs into the PCF and WebResources folder as usual... &lt;strong&gt;Just a tip&lt;/strong&gt;: remember that the value of the &lt;code&gt;name&lt;/code&gt; node of the &lt;code&gt;package.json&lt;/code&gt; files under each of your PCFs/WRs &lt;strong&gt;must&lt;/strong&gt; be unique, otherwise you'll get an error while running &lt;code&gt;npm &amp;lt;whatever&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When you'll run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All dependencies will be installed in a single, above all, &lt;code&gt;node_modules&lt;/code&gt; folder created just under the &lt;code&gt;my-monorepo&lt;/code&gt; folder.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: If two packages need &lt;strong&gt;different versions of the same dep&lt;/strong&gt;, npm may place some deps locally. That’s fine; you’ll still save tons of space overall.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🗂️ A Practical Folder Structure for Power Platform Projects
&lt;/h2&gt;

&lt;p&gt;Here’s a realistic example of the output folder you'll get, on a standard Power App project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;📂 project-folder
├─ 📄 .gitignore
├─ 📂 src
│  ├─ 📄 package.json
│  ├─ 📄 package-lock.json
│  ├─ 📂 node_modules
│  ├─ 📂 Pcf
│  │  ├─ 📂 NumericInputControl
│  │  │  ├─ 📄 package.json
│  │  │  ├─ 📄 package-lock.json
│  │  │  └─ ...
│  │  ├─ 📂 PurchaseOrderGrid
│  │  │  ├─ 📄 package.json
│  │  │  ├─ 📄 package-lock.json
│  │  │  └─ ...
│  │  └─ 📂 CustomTreeView
│  │     ├─ 📄 package.json
│  │     ├─ 📄 package-lock.json
│  │     └─ ...
│  └─ 📂 WebResources
│     ├─ 📂 HomePage
│     │  ├─ 📄 package.json
│     │  ├─ 📄 package-lock.json
│     │  └─ ...
│     └─ 📂 ControlDashboard
│        ├─ 📄 package.json
│        ├─ 📄 package-lock.json
│        └─ ...
└─ 📂 test
   └─ ... # other stuff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📉 How Much Space Do You Actually Save?
&lt;/h2&gt;

&lt;p&gt;In Power Platform solutions with 8–20 React packages, we’ve seen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;60–80% reduction&lt;/strong&gt; in duplicated dependency files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build times drop&lt;/strong&gt; (less overall I/O and fewer cold installs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your mileage varies with library choices, but the direction is always the same: less bloat, more flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  🪄 Conclusions
&lt;/h2&gt;

&lt;p&gt;AI copilot tools make building &lt;strong&gt;custom&lt;/strong&gt;, &lt;strong&gt;crisp UX&lt;/strong&gt; easy. &lt;strong&gt;NPM Workspaces&lt;/strong&gt; make maintaining many customizations sane. Put them together and you’ve got a Power Platform development lifecycle that’s fast, clean, and—dare I say—fun.&lt;/p&gt;

&lt;p&gt;Give it a try, and let me know in the comments what you think about it!&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>powerfuldevs</category>
      <category>powerapps</category>
      <category>npm</category>
    </item>
    <item>
      <title>Automatically Refresh Forms After Status Changes in Model Driven Apps</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Fri, 26 Dec 2025 14:16:52 +0000</pubDate>
      <link>https://forem.com/_neronotte/automatically-refresh-forms-after-status-changes-in-model-driven-apps-ig</link>
      <guid>https://forem.com/_neronotte/automatically-refresh-forms-after-status-changes-in-model-driven-apps-ig</guid>
      <description>&lt;p&gt;When designing complex state models in Model Driven Apps, sometimes it can happen you need to "&lt;strong&gt;freeze&lt;/strong&gt;" the possibility for the user to update a record data &lt;strong&gt;while some operation runs in background&lt;/strong&gt;, and then make it editable again when the operation completes.&lt;/p&gt;

&lt;p&gt;A few practical examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Document Generation and E-signature&lt;/strong&gt;: A quote record triggers automatic PDF generation and sends it to DocuSign or Adobe Sign. While the document is being generated, sent, and awaiting signature, the contract terms should be locked. Once the signature is complete (or rejected), the form becomes editable again for next steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credit Check Integration&lt;/strong&gt;: When a customer service rep creates a new customer account, an external credit check service is called in the background. The form needs to be frozen while waiting for the credit score, payment terms recommendations, and risk assessment to return. You don't want users changing customer details while the credit bureau is still processing the original information.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inventory Reservation in Order Processing&lt;/strong&gt;: When an order is placed, a background process checks inventory across multiple warehouses and reserves stock. During this reservation process (which might involve calling external warehouse management systems), the order quantities should be frozen to prevent overselling or conflicts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance and Regulatory Checks&lt;/strong&gt;: In healthcare or financial services, when a case is submitted, it might trigger automated compliance screenings (sanctions lists, fraud detection, regulatory reporting). The case details must remain locked during these checks to maintain audit trail integrity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Enrichment from Third-party APIs&lt;/strong&gt;: A lead record triggers enrichment processes pulling company data from LinkedIn, Clearbit, or Dun &amp;amp; Bradstreet. While these APIs are called and data is being validated and merged, you want to prevent users from manually entering conflicting information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Typically, you can leverage the out of the box &lt;code&gt;statecode&lt;/code&gt; and &lt;code&gt;statuscode&lt;/code&gt; fields of the interested table to model the &lt;strong&gt;frozen&lt;/strong&gt; statuses. If we take for instance the first example above, a possible state model for the quote table can be the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuy30uv7435a7f9cdmo3l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuy30uv7435a7f9cdmo3l.png" alt="State model representation" width="573" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Logical Status&lt;/th&gt;
&lt;th&gt;&lt;code&gt;statecode&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;statuscode&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;New&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;td&gt;The quote is being created and filled with data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Submitted&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Inactive&lt;/td&gt;
&lt;td&gt;Submitted&lt;/td&gt;
&lt;td&gt;The quote has been submitted for the digital signature, the background operation is running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Signed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;td&gt;Signed&lt;/td&gt;
&lt;td&gt;The quote is open for post-signature steps (approvals, project generation, etc)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cancelled&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Inactive&lt;/td&gt;
&lt;td&gt;Cancelled&lt;/td&gt;
&lt;td&gt;The quote has been logically removed from the system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Inactive&lt;/td&gt;
&lt;td&gt;Sent&lt;/td&gt;
&lt;td&gt;Quote sent to the client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Won&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Inactive&lt;/td&gt;
&lt;td&gt;Won&lt;/td&gt;
&lt;td&gt;The client accepted the quote&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Inactive&lt;/td&gt;
&lt;td&gt;Lost&lt;/td&gt;
&lt;td&gt;The client has rejected the quote&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Where the meaning of the &lt;code&gt;statecode&lt;/code&gt;=&lt;code&gt;Inactive&lt;/code&gt; is "not updatable by the user". I often prefer to change the label of that state to "Read only", as I find this wording less misleading and more intuitive.&lt;/p&gt;

&lt;p&gt;If the trigger that changes the status to the inactive one comes from the form, we have a small UX problem: the form will be disabled (as required), the operation will run in the background (as required), but when the operation ends (after a couple of seconds, potentially), the user doesn't have any clue about the completeness. He needs to manually trigger the form refresh to see the changes to the current record.&lt;/p&gt;

&lt;p&gt;Of course you can leverage &lt;a href="https://learn.microsoft.com/en-us/power-apps/developer/model-driven-apps/clientapi/send-in-app-notifications?tabs=clientapi" rel="noopener noreferrer"&gt;MDA Push Notification system&lt;/a&gt; in the async workflow to notify the user but... he still needs to reopen the record to see the changes.&lt;/p&gt;

&lt;p&gt;Fixing this UX issue is quite easy, you can just add a few lines of &lt;strong&gt;JavaScript&lt;/strong&gt; in the form that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;runs only when the status changes to the inactive one you want to monitor&lt;/li&gt;
&lt;li&gt;checks iteratively via WebApi for the status to change&lt;/li&gt;
&lt;li&gt;when the status changes, refreshes the form data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;just like the following one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;formType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ReadOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Disabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;BulkEdit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nx"&gt;statuscode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;New&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Submitted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Polling interval for status monitoring&lt;/span&gt;
    &lt;span class="nx"&gt;pollingInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;onLoad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;executionContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;executionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFormContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFormType&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Start auto-refresh monitoring if status is Submitted&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statuscode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Submitted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startStatusMonitoring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addOnLoad&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startStatusMonitoring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Starts monitoring the record status if it's in Initializing state
     * @param {object} formContext - The form context
     */&lt;/span&gt;
    &lt;span class="nf"&gt;startStatusMonitoring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;statuscode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Only start monitoring if status is Submitted&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentStatus&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Starting status monitoring for Initializing record&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Clear any existing interval&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pollingInterval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pollingInterval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Start polling every 2 seconds&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pollingInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkStatusChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error checking status change:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="c1"&gt;// Continue polling even if there's an error&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Checks if the record status has changed and refreshes the page if it has
     * @param {object} formContext - The form context
     */&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;checkStatusChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recordId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getId&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entityName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEntityName&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Retrieve the current statuscode from the server&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Xrm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WebApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieveRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entityName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;recordId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;?$select=statuscode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;serverStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statuscode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentFormStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;statuscode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Current form status: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currentFormStatus&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, Server status: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;serverStatus&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// If status has changed from Submitted, refresh the page&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentFormStatus&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;serverStatus&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Status changed, refreshing page...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="c1"&gt;// Clear the polling interval&lt;/span&gt;
                &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopStatusMonitoring&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

                &lt;span class="c1"&gt;// Refresh the current window&lt;/span&gt;
                &lt;span class="nx"&gt;formContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to retrieve record status:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// Don't stop polling on API errors, as they might be temporary&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Stops the status monitoring (useful for cleanup)
     */&lt;/span&gt;
    &lt;span class="nf"&gt;stopStatusMonitoring&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pollingInterval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pollingInterval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pollingInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Status monitoring stopped&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;Greg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Greg&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="nx"&gt;Greg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;greg_quote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Greg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;greg_quote&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="nx"&gt;Greg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;greg_quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hope this helps you too!&lt;/p&gt;

</description>
      <category>powerapps</category>
      <category>powerplatform</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to: Fix WebResources Layout in Dataverse Dashboards</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Tue, 18 Nov 2025 22:59:07 +0000</pubDate>
      <link>https://forem.com/_neronotte/how-to-fix-webresources-layout-in-dataverse-dashboards-3646</link>
      <guid>https://forem.com/_neronotte/how-to-fix-webresources-layout-in-dataverse-dashboards-3646</guid>
      <description>&lt;h2&gt;
  
  
  🤔 The problem
&lt;/h2&gt;

&lt;p&gt;If you ever managed to create a &lt;strong&gt;Dataverse Dashboard containing an HTML WebResource&lt;/strong&gt; (e.g. to show some calculated KPI), by default the resulting &lt;strong&gt;UI is quite ugly&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa6yaua2mvhs11f0ulmxt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa6yaua2mvhs11f0ulmxt.png" alt="UI" width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What I don't like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each WebResource component has a &lt;strong&gt;surrounding black border&lt;/strong&gt; I would like to get rid of&lt;/li&gt;
&lt;li&gt;The minimum &lt;strong&gt;vertical space&lt;/strong&gt; that can be occupied by the WebResources row is 6 lines. Too much for my needs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For both points, the Dashboard editor doesn't provides any option to fix the problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No option to remove the border&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdukn97bsk8pxst02kbck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdukn97bsk8pxst02kbck.png" alt="No option to remove the border" width="800" height="634"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;And I cannot reduce the vertical space&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8y1js5wkpjffena59fn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8y1js5wkpjffena59fn.png" alt="Cannot reduce the vertical space" width="484" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 The solution
&lt;/h2&gt;

&lt;p&gt;As usual, the solution is quite simple via small XML tweaking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Create a new solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In my case I already had a "ribbon" solution ready to use, thus in the screenshots below you'll see that one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2. Add the dashboard as the only component&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Access the solution, then click on &lt;strong&gt;Add existing&lt;/strong&gt; &amp;gt; &lt;strong&gt;Dashboard&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz208ygxt9agp9slwkkxt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz208ygxt9agp9slwkkxt.png" alt="Add existing dashboards" width="194" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And peek the dashboard you want to update (in my case, &lt;code&gt;Tickets&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkyis5rxg5ndle2z4ydy9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkyis5rxg5ndle2z4ydy9.png" alt="Peek the right dashboard" width="334" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3. Move to VS Code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In VSCode terminal, run the following PAC CLI command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;solution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your solution unique name&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PAC CLI will automatically download the solution and unpack its components in the current folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4. Update the Dashboard XML definition&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Access the dashboard xml definition you'll find at &lt;code&gt;&amp;lt;solution name&amp;gt;\src\Dashboards\&amp;lt;dashboard guid&amp;gt;.xml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69acma0r1jpgmjqxbfn2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F69acma0r1jpgmjqxbfn2.png" alt="Path to dashboard XML" width="323" height="132"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the XML definition, find the WebResources controls. They can be easily found searching by the following string &lt;code&gt;&amp;lt;control id="WebResource_&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You'll see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;cell&lt;/span&gt; &lt;span class="na"&gt;colspan=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;rowspan=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="na"&gt;showlabel=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"{77846b48-5139-4c61-a5d1-fda39d80f21b}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;labels&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;"KPI1"&lt;/span&gt; &lt;span class="na"&gt;languagecode=&lt;/span&gt;&lt;span class="s"&gt;"1033"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/labels&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;control&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"WebResource_kpi1"&lt;/span&gt; &lt;span class="na"&gt;classid=&lt;/span&gt;&lt;span class="s"&gt;"{9FDF5F91-88B1-47f4-AD53-C11EFC01A01D}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;parameters&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Url&amp;gt;&lt;/span&gt;prefix_/pages/dashboardkpi/index.html&lt;span class="nt"&gt;&amp;lt;/Url&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Data&amp;gt;&lt;/span&gt;ticket_total_count&lt;span class="nt"&gt;&amp;lt;/Data&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;PassParameters&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/PassParameters&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ShowOnMobileClient&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/ShowOnMobileClient&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Security&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/Security&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Scrolling&amp;gt;&lt;/span&gt;auto&lt;span class="nt"&gt;&amp;lt;/Scrolling&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Border&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/Border&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;WebResourceId&amp;gt;&lt;/span&gt;{F30B702C-C9C4-F011-BBD3-7CED8D454E57}&lt;span class="nt"&gt;&amp;lt;/WebResourceId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/parameters&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/control&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/cell&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To remove the border, update the &lt;code&gt;&amp;lt;Border&amp;gt;&lt;/code&gt; node to &lt;code&gt;false&lt;/code&gt;.&lt;br&gt;
To decrease the height, set the &lt;code&gt;rowspan&lt;/code&gt; attribute to &lt;code&gt;3&lt;/code&gt; (or adjust it as you wish).&lt;/p&gt;

&lt;p&gt;Like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;cell&lt;/span&gt; &lt;span class="na"&gt;colspan=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;rowspan=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; &lt;span class="na"&gt;showlabel=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"{77846b48-5139-4c61-a5d1-fda39d80f21b}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;labels&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;"KPI1"&lt;/span&gt; &lt;span class="na"&gt;languagecode=&lt;/span&gt;&lt;span class="s"&gt;"1033"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/labels&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;control&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"WebResource_kpi1"&lt;/span&gt; &lt;span class="na"&gt;classid=&lt;/span&gt;&lt;span class="s"&gt;"{9FDF5F91-88B1-47f4-AD53-C11EFC01A01D}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;parameters&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Url&amp;gt;&lt;/span&gt;prefix_/pages/dashboardkpi/index.html&lt;span class="nt"&gt;&amp;lt;/Url&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Data&amp;gt;&lt;/span&gt;ticket_total_count&lt;span class="nt"&gt;&amp;lt;/Data&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;PassParameters&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/PassParameters&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ShowOnMobileClient&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/ShowOnMobileClient&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Security&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/Security&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Scrolling&amp;gt;&lt;/span&gt;auto&lt;span class="nt"&gt;&amp;lt;/Scrolling&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Border&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/Border&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;WebResourceId&amp;gt;&lt;/span&gt;{F30B702C-C9C4-F011-BBD3-7CED8D454E57}&lt;span class="nt"&gt;&amp;lt;/WebResourceId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/parameters&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/control&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/cell&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5. Rebuild the solution in unmanaged mode&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open the terminal in the folder containing the &lt;code&gt;&amp;lt;solution&amp;gt;.cdsproj&lt;/code&gt; file and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it will generate the unmanaged version of the solution in the &lt;code&gt;\bin\Debug&lt;/code&gt; subfolder of the current folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6. Import the generated solution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Via PAC CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ribbon is the name of my solution, change it with your own&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;solution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\bin\debug\ribbon.zip&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 7. Publish all&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This time, let's use PACX&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pacx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can always do it the old way, via maker portal&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  😎 The result
&lt;/h2&gt;

&lt;p&gt;Et voilà, the same dashboard, with both issues fixed ☺️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhf8kv1bi1rx0hnk2dfu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhf8kv1bi1rx0hnk2dfu.png" alt="Final result" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hope this helps you too!&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>powerfuldevs</category>
    </item>
    <item>
      <title>Model Driven App: disable "Open in Excel Online" button</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Thu, 13 Nov 2025 16:17:20 +0000</pubDate>
      <link>https://forem.com/_neronotte/model-driven-app-disable-open-in-excel-online-button-15j9</link>
      <guid>https://forem.com/_neronotte/model-driven-app-disable-open-in-excel-online-button-15j9</guid>
      <description>&lt;h2&gt;
  
  
  🤔 The issue
&lt;/h2&gt;

&lt;p&gt;The out-of-the-box Excel integration in Model Driven apps is a powerful, but &lt;em&gt;very dangerous&lt;/em&gt;, feature.&lt;/p&gt;

&lt;p&gt;Import from Excel, especially, can be very harmful because an user could leverage it to bypass any client side logic put on forms, generating inconsistent data within the system. Moreover, it cannot be customized: you cannot say "you can import opportunities records, but you cannot import accounts", neither you could say "import works only on columns A, B, C".&lt;/p&gt;

&lt;p&gt;That's the reason why, in most of the CRM (and MDAs) implementations I've seen so far, the &lt;strong&gt;Import from Excel button is disabled for standard users&lt;/strong&gt;, and &lt;strong&gt;replaced with custom data-import&lt;/strong&gt; capabilities.&lt;/p&gt;

&lt;p&gt;Disabling the &lt;strong&gt;Import from Excel&lt;/strong&gt; button is quite easy, It just requires from you to remove the &lt;strong&gt;Data Import&lt;/strong&gt; and &lt;strong&gt;Data Map&lt;/strong&gt; privileges from the role assigned to users.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpevdsqw31issmqcbjr8s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpevdsqw31issmqcbjr8s.png" alt="Data Import and Data Map privileges removed from roles" width="800" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It works... more or less...&lt;/p&gt;

&lt;p&gt;...even if the Data Import privilege is removed, you can have the possibility to open the "Export In Excel" &amp;gt; "Open in Excel Online" capability&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrsfbhc6olvqygum8m9j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwrsfbhc6olvqygum8m9j.png" alt="Open in Excel Online" width="347" height="83"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the edit capability enabled, and the &lt;strong&gt;Save&lt;/strong&gt; button &lt;strong&gt;visible and clickable&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fepcwqsb1mnm4oaels7ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fepcwqsb1mnm4oaels7ck.png" alt="Save button" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you change something and click on Save, the following (ugly) error message appears (depending on the UI language of the user, mine is Italian)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F63m2kpl3g9tl6tr67dpg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F63m2kpl3g9tl6tr67dpg.png" alt="Error message" width="400" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's a behavior that &lt;strong&gt;looks more like a bug than a feature&lt;/strong&gt;, IMHO.&lt;/p&gt;

&lt;p&gt;The best would be to &lt;strong&gt;disable that "Open in Excel Online" button too... tipically for specific users/roles&lt;/strong&gt;. But there is no such option in the role privileges...&lt;/p&gt;

&lt;h2&gt;
  
  
  😖 Failed fix tentatives
&lt;/h2&gt;

&lt;p&gt;If you try to hide the button using Ribbon Workbench or the Modern Ribbon editor, you will quickly see that's &lt;strong&gt;any customization on that button is disabled&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ao26kamobjw7r2zunmc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ao26kamobjw7r2zunmc.png" alt="Disabled" width="427" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  👍🏻 How to do it
&lt;/h2&gt;

&lt;p&gt;For people like me, mold by CRM implementations before Ribbon Workbench, the fix is quite straightforward: &lt;strong&gt;let's tweak the RibbonDiffXml "the old way"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;First of all, let's create a solution containing only the definition of the entity we want to disable that button for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffl2xfz5sm65o8ce54tqk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffl2xfz5sm65o8ce54tqk.png" alt="Temp solution" width="559" height="105"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6g4ekshkef9n36a8mdhk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6g4ekshkef9n36a8mdhk.png" alt="Temp solution" width="229" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then let's use &lt;code&gt;Power Platform CLI&lt;/code&gt; commands to export and unpack the solution in a local folder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;solution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It generates locally a structure similar to the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkihqswcfrqnfiqlhbpl9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkihqswcfrqnfiqlhbpl9.png" alt="Exported solution" width="266" height="173"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's open the &lt;code&gt;RibbonDiff.xml&lt;/code&gt; file, it will be like the following one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;RibbonDiffXml&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;CustomActions&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Templates&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;RibbonTemplates&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.Templates"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/RibbonTemplates&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Templates&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;CommandDefinitions&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;RuleDefinitions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TabDisplayRules&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;DisplayRules&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;EnableRules&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/RuleDefinitions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;LocLabels&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/RibbonDiffXml&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;&amp;lt;DisplayRules&amp;gt;&lt;/code&gt; node, let's add a rule that is evaluated to "true" only in the case an user is system administrator (here you can use your fantasy)...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;DisplayRules&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"greg.tablename.IsSystemAdmin.DisplayRule"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;EntityPrivilegeRule&lt;/span&gt; &lt;span class="na"&gt;PrivilegeType=&lt;/span&gt;&lt;span class="s"&gt;"Delete"&lt;/span&gt; &lt;span class="na"&gt;PrivilegeDepth=&lt;/span&gt;&lt;span class="s"&gt;"Basic"&lt;/span&gt; &lt;span class="na"&gt;EntityName=&lt;/span&gt;&lt;span class="s"&gt;"solution"&lt;/span&gt; &lt;span class="na"&gt;Default=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;InvertResult=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/DisplayRule&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/DisplayRules&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in the &lt;code&gt;&amp;lt;CommandDefinitions&amp;gt;&lt;/code&gt; node, let's add the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;CommandDefinitions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;CommandDefinition&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.ExportToExcel.Online"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;EnableRules&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;EnableRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.ExportToExcel.ValidForXlsxExport"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;EnableRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.EnableExportToExcelOnlineForModern"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;EnableRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.EnableOnlyInBrowsersForModern"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/EnableRules&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;DisplayRules&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.Live"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.IsExportToExcelFCBEnabled"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.HideOnOutlookClient"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.NotAdvancedFind"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.HideOnPhoneForNonModern"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.HideForTabletExperience"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"greg.tablename.IsSystemAdmin.DisplayRule"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/DisplayRules&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Actions&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;JavaScriptFunction&lt;/span&gt; &lt;span class="na"&gt;FunctionName=&lt;/span&gt;&lt;span class="s"&gt;"XrmCore.Commands.Export.exportToExcel"&lt;/span&gt; &lt;span class="na"&gt;Library=&lt;/span&gt;&lt;span class="s"&gt;"$webresource:Main_system_library.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;CrmParameter&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"SelectedControl"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;CrmParameter&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"SelectedEntityTypeName"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;IntParameter&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/JavaScriptFunction&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Actions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/CommandDefinition&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/CommandDefinitions&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final RibbonDiffXML should look something similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;RibbonDiffXml&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;CustomActions&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Templates&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;RibbonTemplates&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.Templates"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/RibbonTemplates&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Templates&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;CommandDefinitions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;CommandDefinition&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.ExportToExcel.Online"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;EnableRules&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;EnableRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.ExportToExcel.ValidForXlsxExport"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;EnableRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.EnableExportToExcelOnlineForModern"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;EnableRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.EnableOnlyInBrowsersForModern"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/EnableRules&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRules&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.Live"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.IsExportToExcelFCBEnabled"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.HideOnOutlookClient"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.NotAdvancedFind"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.HideOnPhoneForNonModern"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"Mscrm.HideForTabletExperience"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"greg.tablename.IsSystemAdmin.DisplayRule"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/DisplayRules&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Actions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;JavaScriptFunction&lt;/span&gt; &lt;span class="na"&gt;FunctionName=&lt;/span&gt;&lt;span class="s"&gt;"XrmCore.Commands.Export.exportToExcel"&lt;/span&gt; &lt;span class="na"&gt;Library=&lt;/span&gt;&lt;span class="s"&gt;"$webresource:Main_system_library.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;CrmParameter&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"SelectedControl"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;CrmParameter&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"SelectedEntityTypeName"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;IntParameter&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/JavaScriptFunction&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/Actions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/CommandDefinition&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/CommandDefinitions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;RuleDefinitions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TabDisplayRules&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;DisplayRules&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;DisplayRule&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"greg.tablename.IsSystemAdmin.DisplayRule"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;EntityPrivilegeRule&lt;/span&gt; &lt;span class="na"&gt;PrivilegeType=&lt;/span&gt;&lt;span class="s"&gt;"Delete"&lt;/span&gt; &lt;span class="na"&gt;PrivilegeDepth=&lt;/span&gt;&lt;span class="s"&gt;"Basic"&lt;/span&gt; &lt;span class="na"&gt;EntityName=&lt;/span&gt;&lt;span class="s"&gt;"solution"&lt;/span&gt; &lt;span class="na"&gt;Default=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;InvertResult=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/DisplayRule&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/DisplayRules&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;EnableRules&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/RuleDefinitions&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;LocLabels&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/RibbonDiffXml&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now build the solution ad upload the generated solution zip file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;solution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\bin\debug\temp.zip&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for the import to complete, then go to your environment, select the solution you just uploaded, and click &lt;strong&gt;Publish All Customizations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89pfvtpl49kxjqdqzsop.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89pfvtpl49kxjqdqzsop.png" alt="Publish all" width="207" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or, if you're using PACX in your project, simply type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pacx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it... Now that button will be visible only by System Administrator.&lt;/p&gt;

&lt;p&gt;Hope this helps you too!&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>powerfuldevs</category>
      <category>powerapps</category>
    </item>
    <item>
      <title>Dataverse MCP Server configuration failed...</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Thu, 06 Nov 2025 20:35:04 +0000</pubDate>
      <link>https://forem.com/_neronotte/dataverse-mcp-server-configuration-failed-3ip5</link>
      <guid>https://forem.com/_neronotte/dataverse-mcp-server-configuration-failed-3ip5</guid>
      <description>&lt;p&gt;If you want to configure the Dataverse MCP Server as described in the &lt;a href="https://learn.microsoft.com/en-us/power-apps/maker/data-platform/data-platform-mcp" rel="noopener noreferrer"&gt;official Microsoft Learn article&lt;/a&gt;, you'll need to write in your &lt;code&gt;mcp.json&lt;/code&gt; file an url with the following &lt;em&gt;exact format&lt;/em&gt; (without the line breaks, of course):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://make.powerautomate.com/environments/
59fd5b0e-f1e3-4f86-920d-509f44cb1846/connections
?apiName=shared_commondataserviceforapps
&amp;amp;connectionName=263a3d1e-1279-45f6-a593-070f9efaaad3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tutorial instructs you to &lt;strong&gt;create a connection&lt;/strong&gt; via &lt;a href="https://make.powerautomate.com/" rel="noopener noreferrer"&gt;make.powerautomate.com&lt;/a&gt;, and then &lt;strong&gt;take the URL from the connection page&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;If you &lt;strong&gt;do it exactly as stated in the official docs&lt;/strong&gt;, the URL you'll get has this form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://make.powerautomate.com/environments/
59fd5b0e-f1e3-4f86-920d-509f44cb1846/connections
/shared_commondataserviceforapps
/263a3d1e-1279-45f6-a593-070f9efaaad3/details
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fact is that URL, put in the &lt;code&gt;mcp.json&lt;/code&gt; file, &lt;strong&gt;won't work&lt;/strong&gt; 😡🤬. &lt;/p&gt;

&lt;p&gt;You need to tweak the url manually in the following way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;take the first part of the url, from &lt;code&gt;https://&lt;/code&gt; to &lt;code&gt;connections&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;replace the &lt;code&gt;/&lt;/code&gt; after &lt;code&gt;connections&lt;/code&gt; with &lt;code&gt;?apiName=&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;replace the &lt;code&gt;/&lt;/code&gt; after &lt;code&gt;shared_commondataserviceforapps&lt;/code&gt; with &lt;code&gt;&amp;amp;connectionName=&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;remove the leading &lt;code&gt;/details&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And you're ready to go. &lt;/p&gt;

&lt;p&gt;🙏🏻 Hope this helps you too!&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>powerfuldevs</category>
      <category>mcp</category>
    </item>
    <item>
      <title>💵 Dataverse Exchange Rates 101 💵</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Mon, 03 Nov 2025 10:31:37 +0000</pubDate>
      <link>https://forem.com/_neronotte/dataverse-exchange-rates-101-3h7h</link>
      <guid>https://forem.com/_neronotte/dataverse-exchange-rates-101-3h7h</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.linkedin.com/posts/sabihahmedkhan_dynamics365-dataverse-exchangerates-activity-7370342873579372545-f3lL/" rel="noopener noreferrer"&gt;See the original post from Sabih Ahmed Khan&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Dynamics 365, exchange rates are not always recalculated automatically:&lt;/p&gt;

&lt;h2&gt;
  
  
  🔹 When Exchange Rate IS Applied
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;At the moment a record with currency fields is created.&lt;/li&gt;
&lt;li&gt;When a money column in the record is updated.&lt;/li&gt;
&lt;li&gt;When the state of the record changes (e.g., draft → submitted).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔹 When Exchange Rate is NOT Applied
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Simply changing a non-money field (like quantity) doesn’t trigger recalculation.&lt;/li&gt;
&lt;li&gt;Viewing a record later still shows values based on the exchange rate at the time it was last assigned, not the latest rate.&lt;/li&gt;
&lt;li&gt;Currency updates in the system don’t retroactively update existing records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 Takeaway: Exchange rates are point-in-time values unless the record’s money fields or state are updated.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to properly manage exchange rate values, see my previous blog post &lt;a href="https://dev.to/_neronotte/update-currency-exchange-rate-in-dataverse-simplified-4big"&gt;Update currency Exchange Rate in Dataverse... simplified! 😊&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>powerplatform</category>
      <category>powerfuldevs</category>
      <category>powerapps</category>
      <category>powerautomate</category>
    </item>
    <item>
      <title>Generative Pages: The Right Hype, for the Wrong Reason</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Sat, 25 Oct 2025 15:51:04 +0000</pubDate>
      <link>https://forem.com/_neronotte/generative-pages-the-right-hype-for-the-wrong-reason-3ho</link>
      <guid>https://forem.com/_neronotte/generative-pages-the-right-hype-for-the-wrong-reason-3ho</guid>
      <description>&lt;p&gt;Lately, the Power Platform community has been &lt;em&gt;buzzing with excitement&lt;/em&gt; over &lt;strong&gt;Generative Pages&lt;/strong&gt;. Touted as a game-changer, they’re often presented as the &lt;em&gt;long-awaited solution for building custom interfaces&lt;/em&gt; within Model-Driven Apps. And while the enthusiasm is understandable, I can’t help but feel that the hype is—at least partially—misplaced.&lt;/p&gt;

&lt;p&gt;Let me explain.&lt;/p&gt;




&lt;h2&gt;
  
  
  🦄 The Myth: “Finally, Custom UI in Model-Driven Apps!”
&lt;/h2&gt;

&lt;p&gt;A recurring narrative I’ve seen is that Generative Pages are being celebrated as the first real way to build custom UIs in Model-Driven Apps. But for those of us who’ve been around since the Dynamics CRM days, this isn’t exactly new territory.&lt;/p&gt;

&lt;p&gt;We’ve &lt;em&gt;always&lt;/em&gt; had the ability to &lt;strong&gt;create custom interfaces&lt;/strong&gt; using &lt;strong&gt;HTML WebResources&lt;/strong&gt;. Whether it was jQuery in the early days or more modern stacks like &lt;strong&gt;React + TypeScript + Fluent UI&lt;/strong&gt;, the platform has long supported advanced UI customization. The only difference? Back then, you had to roll up your sleeves and build it yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 The Reality: Generative Pages Are a New Abstraction, Not a New Capability
&lt;/h2&gt;

&lt;p&gt;Generative Pages are a fantastic addition—don’t get me wrong. They lower the barrier to entry, especially for makers and newcomers who might not be familiar with the extensibility model of Dataverse. But they’re not a replacement for pro-code solutions. They’re a new abstraction layer, not a new frontier.&lt;/p&gt;

&lt;p&gt;If you’re looking for true flexibility, performance, and control, &lt;strong&gt;custom WebResources&lt;/strong&gt; still reign supreme. And if you’re wondering how to get started with those, I’ve written extensively on the topic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/_neronotte/dataverse-web-resources-with-react-typescript-and-fluentui-neronottes-way-3n40"&gt;Dataverse Web Resources with React, Typescript and FluentUI (_neronotte's way)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/_neronotte/form-based-dataverse-web-resources-with-react-typescript-and-fluentui-1d44"&gt;Form-based Dataverse Web Resources with React, Typescript and FluentUI - Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/_neronotte/form-based-dataverse-web-resources-with-react-typescript-and-fluentui-part-2-84d"&gt;Form-based Dataverse Web Resources with React, Typescript and FluentUI - Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/_neronotte/dataverse-web-resources-say-goodbye-to-cra-hello-to-vite-467k"&gt;Say Goodbye to CRA, Hello to Vite&lt;/a&gt; - the last but most important, because the previous articles rely on an NPM template that no longer exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These posts walk through everything from scaffolding your project to handling context injection and deploying with PACX. They’re not just tutorials—they’re a testament to the maturity and power of the platform’s extensibility model.&lt;/p&gt;

&lt;p&gt;And they were &lt;strong&gt;written before the Generative AI era&lt;/strong&gt;. Now you can also leverage GitHub Copilot capabilities in VS or VS Code to implement the logic, making the gap with Generative Pages even lower.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧪 WebResources vs PCFs: The Debugging and Testing Advantage
&lt;/h3&gt;

&lt;p&gt;Another point that often gets overlooked in the Generative Pages vs. WebResources vs. PCF debate is the &lt;strong&gt;developer experience&lt;/strong&gt;—especially when it comes to &lt;strong&gt;debugging and testing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Compared to &lt;strong&gt;PCFs (PowerApps Component Framework)&lt;/strong&gt;, WebResources are significantly easier to debug. You can run them in the browser, inspect them with standard dev tools, and iterate quickly without the need for complex build pipelines or deployment steps. This makes them ideal for rapid prototyping and iterative development.&lt;/p&gt;

&lt;p&gt;PCFs, while powerful, come with a steeper learning curve and a more rigid development lifecycle. Debugging often requires attaching to the browser process, dealing with iframes, and managing a more complex deployment model. For many use cases, especially those that don’t require deep integration with the control lifecycle, WebResources are simply more efficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  💾 Source Control and DevOps: Still a Win for WebResources
&lt;/h3&gt;

&lt;p&gt;One of the lesser-known advantages of WebResources is how well they integrate with &lt;strong&gt;source control and CI/CD pipelines&lt;/strong&gt;. Thanks to tools like &lt;strong&gt;Power Platform CLI&lt;/strong&gt; and &lt;strong&gt;PACX&lt;/strong&gt;, you can manage your WebResource projects in Git, automate deployments, and maintain a clean, versioned history of your codebase.&lt;/p&gt;

&lt;p&gt;This is still a significant advantage over Generative Pages, which—at least for now—lack the same level of DevOps maturity. While Microsoft is making strides in this area, the ability to fully own and manage your codebase remains a key differentiator for WebResources.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌟 The Real Value Behind Generative Pages
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Generative Pages are worth the excitement because they fundamentally shift the accessibility of custom experiences in Model-Driven Apps&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;For years, building tailored interfaces meant diving into pro-code solutions like WebResources or PCF—powerful, but often out of reach for makers and business users. &lt;/p&gt;

&lt;p&gt;Generative Pages change that equation by introducing an abstraction layer that &lt;strong&gt;democratizes UI creation&lt;/strong&gt; without sacrificing Dataverse integration or solution governance. &lt;/p&gt;

&lt;p&gt;They &lt;strong&gt;accelerate time-to-value&lt;/strong&gt;, reduce dependency on specialized front-end skills, and align perfectly with the low-code ethos of Power Platform. &lt;/p&gt;

&lt;p&gt;In short, &lt;strong&gt;they don’t replace pro-code—they complement it&lt;/strong&gt;, enabling organizations to deliver functional, data-centric pages faster and with less friction, while still leaving room for deeper customization when needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤔 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Let’s welcome Generative Pages for what they are: a helpful tool in the toolbox. But let’s also continue to educate and advocate for the powerful capabilities that have been there all along. The Power Platform isn’t just evolving—it’s maturing. And as professionals, it’s our job to guide that evolution with clarity and context.&lt;/p&gt;

&lt;p&gt;If you’re serious about building rich, enterprise-grade experiences in Model-Driven Apps, don’t overlook the power of WebResources. They’re not just legacy—they’re battle-tested, flexible, and still incredibly relevant.&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>powerapps</category>
      <category>powerfuldevs</category>
      <category>dataverse</category>
    </item>
    <item>
      <title>Your Dataverse Solutions Deserve Better Builds (Here’s How)</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Sun, 19 Oct 2025 14:22:37 +0000</pubDate>
      <link>https://forem.com/_neronotte/your-dataverse-solutions-deserve-better-builds-heres-how-3po9</link>
      <guid>https://forem.com/_neronotte/your-dataverse-solutions-deserve-better-builds-heres-how-3po9</guid>
      <description>&lt;p&gt;Based on my personal experience (I have been working on Dynamics CRM / Power Platform since 2009 and have delivered dozens of projects), today a &lt;strong&gt;&lt;em&gt;typical middle-sized&lt;/em&gt;&lt;/strong&gt; CRM / Model Driven App project contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;data model&lt;/strong&gt; made of roughly 30 tables.&lt;/li&gt;
&lt;li&gt;A few dozen &lt;strong&gt;basic Web Resources&lt;/strong&gt; (images, form JS, command bar JS, theme assets, etc.).&lt;/li&gt;
&lt;li&gt;One or more &lt;strong&gt;Plugin Packages&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;One or more &lt;strong&gt;HTML/React Web Resources&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;One or more &lt;strong&gt;PCF controls&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;One or more &lt;strong&gt;Power Automate flows&lt;/strong&gt; (with related &lt;strong&gt;Connection References&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;One or more &lt;strong&gt;Model Driven Apps&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Roles, field security profiles… everything concerning the security model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the typical content of a &lt;strong&gt;middle-sized project&lt;/strong&gt;; obviously there are smaller ones, but also much larger and more complex initiatives, each with its own peculiarities.&lt;/p&gt;

&lt;p&gt;Looking at the structure of my typical medium project, over time I have tried to standardize ALM processes, &lt;em&gt;industrializing&lt;/em&gt; my delivery approach, with the goal of improving (making more efficient) the way I work and the way the people working with me operate.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧱 Solutions
&lt;/h2&gt;

&lt;p&gt;This industrialized approach also involves defining the Dataverse &lt;strong&gt;solutions&lt;/strong&gt; that compose the application in a consistent way: I prefer a &lt;em&gt;layered&lt;/em&gt; approach where solutions are organized by &lt;em&gt;component type&lt;/em&gt;, with a strict and well-defined &lt;em&gt;dependency direction&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;1st principle&lt;/strong&gt;: Each solution contains only and exclusively components that depend on components present in lower-level solutions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Following the &lt;strong&gt;1st principle&lt;/strong&gt;, the solutions I typically define are the following:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0100 - WebResources&lt;/td&gt;
&lt;td&gt;Contains all project Web Resources (images, form and command bar JS, theme, assets, HTML pages, etc.).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0200 - PCF&lt;/td&gt;
&lt;td&gt;Contains custom PCF controls implemented for the project.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0300 - Data Model&lt;/td&gt;
&lt;td&gt;Contains the system data model (tables, columns, forms, views).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0400 - Business Logic&lt;/td&gt;
&lt;td&gt;Contains Plugin Packages, Steps, Custom APIs, Power Automate flows—everything that is business logic.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0500 - Other&lt;/td&gt;
&lt;td&gt;Contains apps, sitemaps, security roles(1), and the rest of the solution components.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(1) For roles a specific discussion is needed: if system forms and views depend on roles, then roles go into the Data Model solution; otherwise they go into "Other".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layering note&lt;/strong&gt;: the order of the layers is based on &lt;em&gt;import prerequisites&lt;/em&gt; (a form may require a Web Resource / PCF already present), not on the functional responsibility flow (UI &lt;em&gt;consuming&lt;/em&gt; the data model). Web Resources and PCF controls do not create formal dependencies toward tables/columns until they are attached to forms, so they can be imported earlier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Naming convention note&lt;/strong&gt;: Each solution starts with a 4-digit number. The first 2 digits represent the current &lt;em&gt;layer&lt;/em&gt;. The second two digits are used when the solution becomes too large and you decide to fragment its content. Example: suppose you have N React Web Resources (which are typically heavier). You might decide to have a separate solution for each one. In that case I would name them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0100 - WebResources - Base&lt;/li&gt;
&lt;li&gt;0101 - WebResources - Home Page&lt;/li&gt;
&lt;li&gt;0102 - WebResources - Monitoring Dashboard&lt;/li&gt;
&lt;li&gt;0103 - WebResources - Customer Details&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔢 Source Versioning
&lt;/h2&gt;

&lt;p&gt;In my projects, both the &lt;strong&gt;source code&lt;/strong&gt; of the custom components I build (Plugins, Web Resources, PCF controls, etc.) and the &lt;strong&gt;solutions&lt;/strong&gt; described earlier—downloaded and unpacked into their minimal components through &lt;code&gt;pac solution sync&lt;/code&gt;—are versioned in a Git repository (Azure DevOps / Visual Studio or GitHub, based on client preference).&lt;/p&gt;

&lt;p&gt;However, when we download solutions locally to version them, we have a problem: they contain all our customizations, including Web Resources (compiled, in the case of &lt;a href="https://dev.to/_neronotte/dataverse-web-resources-say-goodbye-to-cra-hello-to-vite-467k"&gt;React-based Web Resources&lt;/a&gt;) and &lt;code&gt;.nupkg&lt;/code&gt; files of our Plugin Packages.&lt;/p&gt;

&lt;p&gt;Anyone who has studied ALM theory knows one thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;2nd principle&lt;/strong&gt;: The repository must not contain artifacts—only source code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To avoid storing in the repo what are effectively &lt;em&gt;duplicates&lt;/em&gt; of already existing objects (think simple Web Resources) or artifacts (the HTML/JS/CSS of React Web Resources, or plugin &lt;code&gt;.nupkg&lt;/code&gt; files), mapping files come to the rescue (we already discussed them in the article &lt;a href="https://dev.to/_neronotte/a-practical-guide-to-relative-paths-on-solution-mapping-files-hkm"&gt;"Relative Paths in Solution Mapping Files"&lt;/a&gt; and in &lt;a href="https://dev.to/_neronotte/when-plugin-packages-break-your-mapping-files-and-how-to-fix-it-joc"&gt;"When Plugin Packages Break Your Mapping Files (and How to Fix It)"&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The rules for composing and incrementing the build version number are described in the "Version Format" paragraph in the Pipeline section.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧩 And what about PCF?
&lt;/h3&gt;

&lt;p&gt;For the PCF solution the technique I use is slightly different. While in other cases I first create the solution in Dataverse and then obtain a local copy via &lt;code&gt;pac solution clone&lt;/code&gt; / &lt;code&gt;pac solution sync&lt;/code&gt;, for PCF I:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a solution directly locally using &lt;code&gt;pac solution init&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add the PCF project(s) to the solution as references using &lt;code&gt;pac solution add-reference&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Modify the &lt;code&gt;.cdsproj&lt;/code&gt; file adding the &lt;code&gt;PropertyGroup&lt;/code&gt; shown below so that I get a &lt;strong&gt;Managed&lt;/strong&gt; solution if I compile in &lt;strong&gt;Release&lt;/strong&gt; mode, and an &lt;strong&gt;Unmanaged&lt;/strong&gt; one if I compile in &lt;strong&gt;Debug&lt;/strong&gt; mode.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;SolutionPackageType&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(Configuration)' == 'Debug'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Unmanaged&lt;span class="nt"&gt;&amp;lt;/SolutionPackageType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;SolutionPackageType&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(Configuration)' == 'Release'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Managed&lt;span class="nt"&gt;&amp;lt;/SolutionPackageType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;SolutionFileVersion&amp;gt;&amp;lt;/SolutionFileVersion&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;SolutionPackageEnableLocalization&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/SolutionPackageEnableLocalization&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, I just compile the solution in &lt;strong&gt;Release&lt;/strong&gt; mode and push it to Dataverse using &lt;code&gt;pac solution import&lt;/code&gt;… and PCF controls are up and running.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Pipeline
&lt;/h2&gt;

&lt;p&gt;With such an organization of solutions, if we want to manage releases in a deterministic and reproducible way, we should build a build pipeline that performs the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define the version number associated with the current build (&lt;code&gt;Build.BuildNumber&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Compile the React Web Resources.&lt;/li&gt;
&lt;li&gt;Compile the projects related to plugin DLLs to generate the &lt;code&gt;PluginPackage&lt;/code&gt; (&lt;code&gt;.nupkg&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Update the solutions' versions setting them to &lt;code&gt;Build.BuildNumber&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Compile the PCF solution (0200 - PCF) in &lt;code&gt;Release&lt;/code&gt; mode.&lt;/li&gt;
&lt;li&gt;Compile the remaining Dataverse solutions in &lt;code&gt;Release&lt;/code&gt; mode, retrieving:

&lt;ol&gt;
&lt;li&gt;Simple Web Resources from the folder where they reside in the repo.&lt;/li&gt;
&lt;li&gt;React Web Resources from the folder containing the built output generated in step #2.&lt;/li&gt;
&lt;li&gt;Plugin packages from the &lt;code&gt;bin\Release&lt;/code&gt; subfolder of the corresponding project.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;h3&gt;
  
  
  🧮 Version Format
&lt;/h3&gt;

&lt;p&gt;The value used for &lt;code&gt;Build.BuildNumber&lt;/code&gt; (or equivalent runner number) must follow the format &lt;code&gt;&amp;lt;major.minor.patch.build&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The version number of the &lt;code&gt;.nupkg&lt;/code&gt; (Plugin Package) is normally not relevant for import: the environment accepts the update as long as existing plugin types/contracts are not broken. However, the version MUST change (major or minor) when one or more plugin classes previously deployed are physically removed; otherwise you risk hitting the following error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Exception type: System.ServiceModel.FaultException[Microsoft.Xrm.Sdk.OrganizationServiceFault]&lt;/p&gt;

&lt;p&gt;Message: Plugin Assemblies import: FAILURE. Error: Plugin: 'NAME OF PLUGIN ASSEMBLY', Version=&amp;lt;0.1.0.0&amp;gt; ... caused an exception.: Existing plug-in types have been removed. Please update major or minor verion of plug-in assembly. ...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  🔁 Back to the pipeline
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Theoretically&lt;/em&gt; implementing such a pipeline seems quite trivial.&lt;/p&gt;

&lt;p&gt;In reality, when you try to set it up you &lt;strong&gt;hit several problems&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passing the version number to solutions at compile time (via &lt;code&gt;dotnet build&lt;/code&gt;, as we would for any other .NET project) does not work. The version number passed as a parameter is not reflected in the actual solution version. The only way to make the solution actually reflect the current build version is to add a PowerShell script that physically modifies the Solution.xml files, changing the &lt;code&gt;Version&lt;/code&gt; node, manually or through the &lt;code&gt;pac solution version&lt;/code&gt; command.&lt;/li&gt;
&lt;li&gt;Mapping files do not work with Plugin Packages. Or rather… they work during sync, but not during build. You need something that manually copies the &lt;code&gt;.nupkg&lt;/code&gt; files generated by compiling the plugin DLLs into the solution folder before building the solution itself.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.nupkg&lt;/code&gt; files generated by the DLL build, moreover, have a different name than expected by the Dataverse solutions. So you also need a rename step "in the middle," before or after the copy.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;' Package generated by the DLL project build&lt;br&gt;
Greg.Plugins.1.0.0.nupkg&lt;/p&gt;

&lt;p&gt;' "Expected" name by the Dataverse solution&lt;br&gt;
nn_Greg.Plugins.nupkg&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I tried &lt;em&gt;N&lt;/em&gt; different ways to build a general purpose pipeline that performed all necessary steps, overcoming the problems listed above, but honestly none of the solutions I arrived at ever fully satisfied me.&lt;/p&gt;

&lt;p&gt;Too many variables inside the pipeline code.&lt;/p&gt;

&lt;p&gt;Too many steps to manually modify project by project.&lt;/p&gt;

&lt;p&gt;…until…&lt;/p&gt;

&lt;h2&gt;
  
  
  🚨 Betim to the rescue 🚨
&lt;/h2&gt;

&lt;p&gt;My friend &lt;a href="https://www.linkedin.com/in/betimbeja/" rel="noopener noreferrer"&gt;Betim Beja&lt;/a&gt; suggested I try using his &lt;a href="https://github.com/albanian-xrm/CSDPROJ-SDK" rel="noopener noreferrer"&gt;&lt;em&gt;project templates&lt;/em&gt;&lt;/a&gt;, completely open source and available on GitHub.&lt;/p&gt;

&lt;p&gt;For me, &lt;strong&gt;a turning point&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;His &lt;code&gt;AlbanianXrm.CDSProj.Sdk&lt;/code&gt; is an SDK-style Visual Studio project template that can be used to replace the standard definition of &lt;code&gt;.cdsproj&lt;/code&gt; projects automatically generated by &lt;code&gt;pac solution clone&lt;/code&gt; / &lt;code&gt;pac solution sync&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Compared to the "original" Dataverse template, it drastically simplifies managing all the issues I listed above.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⁉️ How to use it
&lt;/h3&gt;

&lt;p&gt;After creating your solution inside Dataverse (manually, or via &lt;a href="https://github.com/neronotte/Greg.Xrm.Command/wiki/pacx-solution-create" rel="noopener noreferrer"&gt;&lt;code&gt;pacx solution create&lt;/code&gt;&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1.&lt;/strong&gt; Clone the solution locally using &lt;code&gt;pac solution clone&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2.&lt;/strong&gt; Open the file &lt;code&gt;solutionname.cdsproj&lt;/code&gt;, which should look similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;ToolsVersion=&lt;/span&gt;&lt;span class="s"&gt;"15.0"&lt;/span&gt; &lt;span class="na"&gt;DefaultTargets=&lt;/span&gt;&lt;span class="s"&gt;"Build"&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.microsoft.com/developer/msbuild/2003"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PowerAppsTargetsPath&amp;gt;&lt;/span&gt;$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\PowerApps&lt;span class="nt"&gt;&amp;lt;/PowerAppsTargetsPath&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Import&lt;/span&gt; &lt;span class="na"&gt;Project=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Import&lt;/span&gt; &lt;span class="na"&gt;Project=&lt;/span&gt;&lt;span class="s"&gt;"$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Solution.props"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Solution.props')"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ProjectGuid&amp;gt;&lt;/span&gt;5aa1879b-b4a3-4734-bcad-d964d642cdff&lt;span class="nt"&gt;&amp;lt;/ProjectGuid&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFrameworkVersion&amp;gt;&lt;/span&gt;v4.6.2&lt;span class="nt"&gt;&amp;lt;/TargetFrameworkVersion&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!--Remove TargetFramework when this is available in 16.1--&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net462&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;RestoreProjectStyle&amp;gt;&lt;/span&gt;PackageReference&lt;span class="nt"&gt;&amp;lt;/RestoreProjectStyle&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;SolutionRootPath&amp;gt;&lt;/span&gt;src&lt;span class="nt"&gt;&amp;lt;/SolutionRootPath&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- 
    Solution Packager overrides, un-comment to use: SolutionPackagerType (Managed, Unmanaged, Both)
    Solution Localization Control, if you want to enabled localization of your solution, un-comment SolutionPackageEnableLocalization and set the value to true. - Requires use of -loc flag on Solution Clone or Sync
  --&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!--
  &amp;lt;PropertyGroup&amp;gt;
    &amp;lt;SolutionPackageType&amp;gt;Managed&amp;lt;/SolutionPackageType&amp;gt;
    &amp;lt;SolutionPackageEnableLocalization&amp;gt;false&amp;lt;/SolutionPackageEnableLocalization&amp;gt;
  &amp;lt;/PropertyGroup&amp;gt;
  --&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.PowerApps.MSBuild.Solution"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.*"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NETFramework.ReferenceAssemblies"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.0.0"&lt;/span&gt; &lt;span class="na"&gt;PrivateAssets=&lt;/span&gt;&lt;span class="s"&gt;"All"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ExcludeDirectories&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildThisFileDirectory)\.gitignore"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ExcludeDirectories&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildThisFileDirectory)\bin\**"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ExcludeDirectories&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildThisFileDirectory)\obj\**"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ExcludeDirectories&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildThisFileDirectory)\*.cdsproj"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ExcludeDirectories&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildThisFileDirectory)\*.cdsproj.user"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ExcludeDirectories&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildThisFileDirectory)\*.sln"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;None&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildThisFileDirectory)\**"&lt;/span&gt; &lt;span class="na"&gt;Exclude=&lt;/span&gt;&lt;span class="s"&gt;"@(ExcludeDirectories)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Content&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"$(SolutionPackageZipFilePath)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;CopyToOutputDirectory&amp;gt;&lt;/span&gt;PreserveNewest&lt;span class="nt"&gt;&amp;lt;/CopyToOutputDirectory&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/Content&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Import&lt;/span&gt; &lt;span class="na"&gt;Project=&lt;/span&gt;&lt;span class="s"&gt;"$(MSBuildToolsPath)\Microsoft.Common.targets"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Import&lt;/span&gt; &lt;span class="na"&gt;Project=&lt;/span&gt;&lt;span class="s"&gt;"$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Solution.targets"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Solution.targets')"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3.&lt;/strong&gt;  Remove all its content and simply replace it with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"AlbanianXrm.CDSProj.Sdk/1.0.5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionPackageType&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(Configuration)' == 'Debug'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Unmanaged&lt;span class="nt"&gt;&amp;lt;/SolutionPackageType&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionPackageType&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(Configuration)' == 'Release'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Managed&lt;span class="nt"&gt;&amp;lt;/SolutionPackageType&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionFileVersion&amp;gt;&lt;/span&gt;$(FileVersion)&lt;span class="nt"&gt;&amp;lt;/SolutionFileVersion&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you're done.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;SolutionFileVersion&lt;/code&gt; node performs the &lt;em&gt;magic&lt;/em&gt; of automatically managing the solution version based on the value passed via &lt;code&gt;dotnet build /p:FileVersion=1.0.1.1&lt;/code&gt;. No manual edits required.&lt;/p&gt;

&lt;h3&gt;
  
  
  🕸️ And the web resources?
&lt;/h3&gt;

&lt;p&gt;If the solution you are managing is the one that contains the web resources, add the mapping file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"AlbanianXrm.CDSProj.Sdk/1.0.5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionPackageMapFilePath&amp;gt;&lt;/span&gt;$(MSBuildThisFileDirectory)map.build.xml&lt;span class="nt"&gt;&amp;lt;/SolutionPackageMapFilePath&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionPackageType&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(Configuration)' == 'Debug'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Unmanaged&lt;span class="nt"&gt;&amp;lt;/SolutionPackageType&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionPackageType&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(Configuration)' == 'Release'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Managed&lt;span class="nt"&gt;&amp;lt;/SolutionPackageType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which, in my case, looks like this (&lt;code&gt;nn&lt;/code&gt; is the publisher prefix):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Mapping&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Folder&lt;/span&gt; &lt;span class="na"&gt;map=&lt;/span&gt;&lt;span class="s"&gt;"WebResources\nn_"&lt;/span&gt; &lt;span class="na"&gt;to=&lt;/span&gt;&lt;span class="s"&gt;"..\..\..\..\..\..\src\WebResources\nn_"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Mapping&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  📦 Nice… but what about the &lt;code&gt;.nupkg&lt;/code&gt; files?
&lt;/h3&gt;

&lt;p&gt;Betim thought of that too. If you want to avoid the whole annoying process of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manually compiling plugin DLLs to generate &lt;code&gt;.nupkg&lt;/code&gt; files.&lt;/li&gt;
&lt;li&gt;Renaming them one by one into the name "expected" by the Dataverse solution.&lt;/li&gt;
&lt;li&gt;Moving them one by one into the appropriate folder where &lt;code&gt;dotnet build&lt;/code&gt; will look when rebuilding the solution (managed or unmanaged).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just add an &lt;code&gt;ItemGroup&lt;/code&gt; like the following to the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ProjectReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"..\..\src\Greg.Plugins\Greg.Plugins.csproj"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Referencing the project created via &lt;code&gt;pac plugin init&lt;/code&gt;, and that's it. The project itself, during build, will do the dirty work for you.&lt;/p&gt;

&lt;p&gt;Below is a complete file example, where I also add a few extra parameters at my discretion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"AlbanianXrm.CDSProj.Sdk/1.0.5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionPackageMapFilePath&amp;gt;&lt;/span&gt;$(MSBuildThisFileDirectory)map.build.xml&lt;span class="nt"&gt;&amp;lt;/SolutionPackageMapFilePath&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionPackageType&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(Configuration)' == 'Debug'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Unmanaged&lt;span class="nt"&gt;&amp;lt;/SolutionPackageType&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionPackageType&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(Configuration)' == 'Release'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Managed&lt;span class="nt"&gt;&amp;lt;/SolutionPackageType&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionFileVersion&amp;gt;&lt;/span&gt;$(FileVersion)&lt;span class="nt"&gt;&amp;lt;/SolutionFileVersion&amp;gt;&lt;/span&gt;

        &lt;span class="c"&gt;&amp;lt;!-- other properties added to manage logging and zip file generation --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionPackageEnableLocalization&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/SolutionPackageEnableLocalization&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionPackageErrorLevel&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(Configuration)' == 'Debug'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Verbose&lt;span class="nt"&gt;&amp;lt;/SolutionPackageErrorLevel&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionPackageErrorLevel&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(Configuration)' == 'Release'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Info&lt;span class="nt"&gt;&amp;lt;/SolutionPackageErrorLevel&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionPackageZipFilePath&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(SolutionFileVersion)' != '' AND '$(Configuration)' == 'Debug'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;$(OutputPath)$(MSBuildProjectName)_v$(SolutionFileVersion)_unmanaged.zip&lt;span class="nt"&gt;&amp;lt;/SolutionPackageZipFilePath&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionPackageZipFilePath&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(SolutionFileVersion)' != '' AND '$(Configuration)' == 'Release'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;$(OutputPath)$(MSBuildProjectName)_v$(SolutionFileVersion)_managed.zip&lt;span class="nt"&gt;&amp;lt;/SolutionPackageZipFilePath&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;SolutionPackageZipFilePath&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(SolutionFileVersion)' == ''"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;$(OutputPath)$(MSBuildProjectName)_v$(SolutionFileVersion).zip&lt;span class="nt"&gt;&amp;lt;/SolutionPackageZipFilePath&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;ProjectReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"..\..\src\Greg.Plugins\Greg.Plugins.csproj"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ✅ Conclusions
&lt;/h2&gt;

&lt;p&gt;In the end the problems that make us waste time in Model Driven projects are always the same: version numbers that communicate nothing, mapping breaking with Plugin Packages, manual rename of &lt;code&gt;.nupkg&lt;/code&gt; files, uneven handling between PCF and Web Resources, PowerShell scripts patching &lt;code&gt;Solution.xml&lt;/code&gt;. All repetitive work that adds no value and we'd rather forget.&lt;/p&gt;

&lt;p&gt;The SDK template &lt;code&gt;AlbanianXrm.CDSProj.Sdk&lt;/code&gt; does most of the heavy lifting: no scripts to force versioning, no manual copy/rename of packages, no “magical” builds different project by project. You reference the plugin project and it prepares the &lt;code&gt;.nupkg&lt;/code&gt; as needed; you pass &lt;code&gt;FileVersion&lt;/code&gt; and the solution aligns; you build and import. Done.&lt;/p&gt;

&lt;p&gt;The Power Platform / Dynamics CRM community is, once again, &lt;strong&gt;that place where someone has already suffered before you and decided to share the cure&lt;/strong&gt;. If today you are still manually renaming files or fixing version numbers at the end of the build, take a test solution, apply the template and measure how much time you save. After a few rounds it will be hard to go back.&lt;/p&gt;

&lt;p&gt;A special thanks to &lt;strong&gt;Betim Beja&lt;/strong&gt; for providing the &lt;code&gt;AlbanianXrm.CDSProj.Sdk&lt;/code&gt; accelerator: it turned several recurring nuisances into details we simply no longer have to manage.&lt;/p&gt;

&lt;p&gt;If you feel like it, let me know what you still miss to make your ALM truly “boring” (and therefore healthy): that is usually where it’s worth investing in the next improvement.&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>powerapps</category>
      <category>powerfuldevs</category>
      <category>devops</category>
    </item>
    <item>
      <title>When Plugin Packages Break Your Mapping Files (And How to Fix It)</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Sun, 05 Oct 2025 09:34:25 +0000</pubDate>
      <link>https://forem.com/_neronotte/when-plugin-packages-break-your-mapping-files-and-how-to-fix-it-joc</link>
      <guid>https://forem.com/_neronotte/when-plugin-packages-break-your-mapping-files-and-how-to-fix-it-joc</guid>
      <description>&lt;h2&gt;
  
  
  🥱 TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;FileToFile&lt;/code&gt; mappings for plugin package &lt;code&gt;.nupkg&lt;/code&gt; files in Dataverse Solution Packager repeatedly throw &lt;code&gt;Error: Value cannot be null. Parameter name: key&lt;/code&gt; during &lt;code&gt;pac solution sync&lt;/code&gt;. Everything else (&lt;code&gt;FileToPath&lt;/code&gt;, WebResources, metadata) works. &lt;br&gt;
Workaround:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add an MSBuild target that creates a stable, versionless &lt;code&gt;ava_Plugins.nupkg&lt;/code&gt; alongside the versioned &lt;code&gt;Plugins.x.y.z.nupkg&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Maintain two mapping files—one without the plugin package line for sync, one (optional) with it for future pack scenarios.&lt;/li&gt;
&lt;li&gt;Use a short PowerShell script to run sync, then clean or regenerate the stable package as needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Result: deterministic builds, clean repository, and an isolated workaround until an upstream fix lands.&lt;/p&gt;


&lt;h2&gt;
  
  
  🚀 1. Introduction
&lt;/h2&gt;

&lt;p&gt;When we work on Dataverse solutions we routinely leverage the Solution Packager mapping file to keep the repository clean and to steer build artifacts. Everything behaves as expected for WebResources and other assets—until we introduce a &lt;code&gt;FileToFile&lt;/code&gt; mapping for a Plugin Package (&lt;code&gt;pluginpackages/.../package/*.nupkg&lt;/code&gt;). Every time we try that, the same failure appears:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Value cannot be null.
Parameter name: key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This post documents the persistent problem, the investigation path, the changes made to the plugin project (&lt;code&gt;Plugins.csproj&lt;/code&gt; in these examples), and the pragmatic workaround we apply each time: maintaining two mapping files plus a tiny automation script.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤷🏻 Name Mismatch Deep Dive (Why a Stable Copy Is Mandatory)
&lt;/h3&gt;

&lt;p&gt;Here's where things get frustrating. Even before we hit the cryptic null-key bug, there's a sneaky naming mismatch that'll drive you crazy if you don't catch it early.&lt;/p&gt;

&lt;p&gt;Picture this: you build your plugin project and get &lt;code&gt;Plugins.1.0.0.nupkg&lt;/code&gt;. Perfect! But when you extract a Dataverse solution, the same logical package shows up as &lt;code&gt;ava_Plugins.nupkg&lt;/code&gt;. Wait, what? 🤔&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Default Naming Rule&lt;/th&gt;
&lt;th&gt;Example Output&lt;/th&gt;
&lt;th&gt;Changes When&lt;/th&gt;
&lt;th&gt;Stable?&lt;/th&gt;
&lt;th&gt;Used By&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Plugin project build (&lt;code&gt;dotnet pack&lt;/code&gt; implicit/explicit)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;PackageId&amp;gt;&lt;/code&gt; + &lt;code&gt;.&lt;/code&gt; + &lt;code&gt;&amp;lt;Version&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Plugins.1.0.0.nupkg&lt;/code&gt; → &lt;code&gt;Plugins.1.0.1.nupkg&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Every version bump&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;NuGet feeds, artifact stores, human traceability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dataverse solution export (&lt;code&gt;pluginpackages&lt;/code&gt; node)&lt;/td&gt;
&lt;td&gt;Publisher prefix + logical package name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ava_Plugins.nupkg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rare (only if solution structure/prefix changes)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Solution Packager re-import, ALM pipelines&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This mismatch will bite you in multiple ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FileToFile&lt;/code&gt; in &lt;code&gt;map.xml&lt;/code&gt; demands exact filenames—no wildcards, no "close enough."&lt;/li&gt;
&lt;li&gt;Every time you bump versions (1.0.0 → 1.0.1), you'd have to update your mapping file manually. In a fast-moving project, that's a recipe for "oops, forgot to update the map again."&lt;/li&gt;
&lt;li&gt;Dataverse doesn't care about your semantic versioning—it wants that stable &lt;code&gt;ava_Plugins.nupkg&lt;/code&gt; name, period.&lt;/li&gt;
&lt;li&gt;Even if Microsoft fixes the null-key bug tomorrow, you still need that predictable filename or you're back to manual map editing hell.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Our approach&lt;/strong&gt;: create a "stable twin" during the build. The MSBuild target spots any &lt;code&gt;Plugins.*.nupkg&lt;/code&gt; and copies it to &lt;code&gt;ava_Plugins.nupkg&lt;/code&gt; (keeping both, or deleting the versioned one if you prefer less clutter). Now your mapping file can reference a filename that never changes, and your versioned packages can still live their best life in artifact repositories.&lt;/p&gt;

&lt;p&gt;We tried other approaches first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Just rename the primary package&lt;/strong&gt; → Nope, then you lose version traceability when you push to feeds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update the map file every release&lt;/strong&gt; → High maintenance nightmare, especially when you're shipping weekly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symlinks or hardlinks&lt;/strong&gt; → Sounds clever until your CI agent doesn't have permission or you hit cross-platform issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual rename after sync&lt;/strong&gt; → Fragile, easy to forget, and definitely not CI-friendly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The copy approach handles the edge cases too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple versioned packages lying around? It grabs them all, picks one to copy, optionally cleans up the rest.&lt;/li&gt;
&lt;li&gt;First build ever? Stable file magically appears.&lt;/li&gt;
&lt;li&gt;Rebuilding the same version? Only copies if the stable file is missing (keeps builds fast).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bottom line: this isn't some fancy optimization—it's the foundation that makes everything else possible. Without that stable filename, you're constantly fighting the tooling instead of building features.&lt;/p&gt;

&lt;p&gt;To address for the &lt;strong&gt;Name mismatch issue&lt;/strong&gt;, we could simply add the following snippet to our &lt;code&gt;Plugins.csproj&lt;/code&gt; file. The snippet simply &lt;strong&gt;renames&lt;/strong&gt; the generated package (&lt;code&gt;Plugins.1.0.0.nupkg&lt;/code&gt;) as expected by the Dataverse solution project (&lt;code&gt;ava_Plugins.nupkg&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Post-build: create stable (publisher-prefixed, versionless) plugin package file
   Needed so Solution Packager mapping (&amp;lt;FileToFile ...&amp;gt; in map.xml) can always
   point to ava_Plugins.nupkg regardless of version increments.
   The actual versioned file is produced by the PowerApps plugin build targets.
   We derive the expected versioned filename from $(FileVersion). --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- PowerApps plugins output packages to bin\Configuration\ not bin\Configuration\TargetFramework\ --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PluginPackageOutputDir&amp;gt;&lt;/span&gt;$(ProjectDir)bin\$(Configuration)\&lt;span class="nt"&gt;&amp;lt;/PluginPackageOutputDir&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Stable filename consumed by map.xml mapping (internal solution file name) --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;StablePluginPackage&amp;gt;&lt;/span&gt;$(PluginPackageOutputDir)ava_Plugins.nupkg&lt;span class="nt"&gt;&amp;lt;/StablePluginPackage&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Toggle: set to false to keep the versioned nupkg alongside the stable one --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;DeleteVersionedOnCopy&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/DeleteVersionedOnCopy&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;Target&lt;/span&gt; &lt;span class="na"&gt;Name=&lt;/span&gt;&lt;span class="s"&gt;"CopyStablePluginPackage"&lt;/span&gt; &lt;span class="na"&gt;AfterTargets=&lt;/span&gt;&lt;span class="s"&gt;"Pack"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(DesignTimeBuild)' != 'true'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"[CopyStablePluginPackage] Looking for packages in: $(PluginPackageOutputDir)"&lt;/span&gt; &lt;span class="na"&gt;Importance=&lt;/span&gt;&lt;span class="s"&gt;"High"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Discover all versioned packages (exclude stable) --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;VersionedPackages&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"$(PluginPackageOutputDir)Plugins.*.nupkg"&lt;/span&gt; &lt;span class="na"&gt;Exclude=&lt;/span&gt;&lt;span class="s"&gt;"$(StablePluginPackage)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"[CopyStablePluginPackage] Found versioned packages: @(VersionedPackages)"&lt;/span&gt; &lt;span class="na"&gt;Importance=&lt;/span&gt;&lt;span class="s"&gt;"High"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- If we have at least one versioned package, pick the first one --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ItemGroup&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'@(VersionedPackages)' != ''"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;CandidatePackage&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"@(VersionedPackages)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Copy first candidate to stable --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Copy&lt;/span&gt; &lt;span class="na"&gt;SourceFiles=&lt;/span&gt;&lt;span class="s"&gt;"@(CandidatePackage)"&lt;/span&gt; &lt;span class="na"&gt;DestinationFiles=&lt;/span&gt;&lt;span class="s"&gt;"$(StablePluginPackage)"&lt;/span&gt; &lt;span class="na"&gt;SkipUnchangedFiles=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'@(CandidatePackage)' != ''"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"[CopyStablePluginPackage] Created stable package from: @(CandidatePackage)"&lt;/span&gt; &lt;span class="na"&gt;Importance=&lt;/span&gt;&lt;span class="s"&gt;"High"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"Exists('$(StablePluginPackage)') and '@(CandidatePackage)' != ''"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Warning&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"[CopyStablePluginPackage] No versioned plugin package found in $(PluginPackageOutputDir)"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'@(VersionedPackages)' == ''"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Delete versioned packages if toggle enabled --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Delete&lt;/span&gt; &lt;span class="na"&gt;Files=&lt;/span&gt;&lt;span class="s"&gt;"@(VersionedPackages)"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'@(VersionedPackages)' != '' and '$(DeleteVersionedOnCopy)' == 'true'"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Message&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"[CopyStablePluginPackage] Deleted versioned packages: @(VersionedPackages)"&lt;/span&gt; &lt;span class="na"&gt;Importance=&lt;/span&gt;&lt;span class="s"&gt;"Low"&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'@(VersionedPackages)' != '' and '$(DeleteVersionedOnCopy)' == 'true'"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📂 2. Why Mapping Files Matter
&lt;/h2&gt;

&lt;p&gt;We have &lt;a href="https://dev.to/_neronotte/a-practical-guide-to-relative-paths-on-solution-mapping-files-hkm"&gt;already discussed this topic here&lt;/a&gt;.&lt;br&gt;
Mapping files give us two high‑value benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep build artifacts (generated or compiled outputs) out of source control by redirecting extracted solution components to curated locations under &lt;code&gt;src/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Let the .NET build (and related tooling) know where to find externally‑mapped artifacts when we later repack or rebuild the solution (deterministic layout).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For WebResources and most XML/JSON metadata, &lt;code&gt;FileToPath&lt;/code&gt; patterns work flawlessly. The friction appears specifically with Plugin Package &lt;code&gt;.nupkg&lt;/code&gt; binaries when using &lt;code&gt;FileToFile&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  🎯 3. The Issue
&lt;/h2&gt;

&lt;p&gt;Here's what we're trying to solve: we want plugin packages out of source control but available for builds. The &lt;code&gt;FileToFile&lt;/code&gt; mapping should accomplish two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;During &lt;code&gt;pac solution sync&lt;/code&gt;&lt;/strong&gt;: Skip creating the plugin package locally (since we're pointing it to an external file)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;During solution builds&lt;/strong&gt;: Let the compiler grab the plugin package from our project's build output instead of the default location&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The ideal workflow: sync extracts everything except the plugin package (which stays out of source control), then when you build the solution, it finds the plugin package in &lt;code&gt;src/Plugins/bin/Debug/ava_Plugins.nupkg&lt;/code&gt; where our MSBuild target puts it. Clean repository, deterministic builds, everyone's happy.&lt;/p&gt;

&lt;p&gt;So you add a &lt;code&gt;FileToFile&lt;/code&gt; mapping to your &lt;code&gt;map.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;FileToFile&lt;/span&gt; &lt;span class="na"&gt;map=&lt;/span&gt;&lt;span class="s"&gt;"pluginpackages\ava_Plugins\package\ava_Plugins.nupkg"&lt;/span&gt;
            &lt;span class="na"&gt;to=&lt;/span&gt;&lt;span class="s"&gt;"..\\..\\..\\..\\..\\src\\Plugins\\bin\\Debug\\ava_Plugins.nupkg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure your stable package exists (more on that later), run &lt;code&gt;pac solution sync --map .\map.xml -loc&lt;/code&gt;, and... boom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: Value cannot be null. Parameter name: key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove that one line? Works perfectly. Add it back? Instant failure. It's like the tooling has a personal vendetta against plugin package remapping.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧪 4. Analysis of the Failure
&lt;/h2&gt;

&lt;p&gt;Observations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The error fires only when a &lt;code&gt;FileToFile&lt;/code&gt; directive targets the Plugin Package &lt;code&gt;.nupkg&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The same mapping file is perfectly accepted later by MSBuild / pack operations (the build does not care about the mapping; it simply uses the stable binary).&lt;/li&gt;
&lt;li&gt;All relative path depth corrections (2, 5, 6 &lt;code&gt;..&lt;/code&gt;) did not resolve the null key error.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;FileToPath&lt;/code&gt; wildcards for pluginpackages (&lt;code&gt;pluginpackages\**&lt;/code&gt;) avoided the exception but did not relocate the binary (Solution Packager still emitted the file into its default folder).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Likely internal cause: &lt;strong&gt;Solution Packager's plugin package handling expects the physical file to remain in its canonical relative location and does not fully support remapping its binary via the map dictionary used during unpack/localization. When the dictionary lookup for the remapped external target occurs, the expected key is absent → null.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 5. The Practical Workaround: Dual Mapping Files
&lt;/h2&gt;

&lt;p&gt;Because the &lt;code&gt;FileToFile&lt;/code&gt; mapping for the plugin package breaks &lt;code&gt;pac solution sync&lt;/code&gt;, but is still desirable for a future pack/build context, we maintain two mapping files:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Contains Plugin &lt;code&gt;FileToFile&lt;/code&gt;?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sync from Dataverse → FS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;map.sync.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build / (Optional future pack)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;map.build.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run sync using &lt;code&gt;map.sync.xml&lt;/code&gt; (no plugin mapping → no error).&lt;/li&gt;
&lt;li&gt;Manually (or via script) remove the extracted nupkg under &lt;code&gt;solutions/.../pluginpackages/.../package/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Build solution; the csproj target recreates stable &lt;code&gt;ava_Plugins.nupkg&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;map.build.xml&lt;/code&gt; only if you later need a pack that references the external stable file (until the PAC CLI issue is fixed, avoid using it for sync).&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  📝 Solution Project Configuration for Build Mapping
&lt;/h3&gt;

&lt;p&gt;To enable the solution project to use the &lt;code&gt;map.build.xml&lt;/code&gt; file during builds, add the following configuration to your &lt;code&gt;master.cdsproj&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;SolutionPackageMapFilePath&amp;gt;&lt;/span&gt;$(MSBuildThisFileDirectory)map.build.xml&lt;span class="nt"&gt;&amp;lt;/SolutionPackageMapFilePath&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;SolutionPackageType&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(Configuration)' == 'Debug'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Unmanaged&lt;span class="nt"&gt;&amp;lt;/SolutionPackageType&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;SolutionPackageType&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(Configuration)' == 'Release'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Managed&lt;span class="nt"&gt;&amp;lt;/SolutionPackageType&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;SolutionPackageErrorLevel&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(Configuration)' == 'Debug'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Verbose&lt;span class="nt"&gt;&amp;lt;/SolutionPackageErrorLevel&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;SolutionPackageErrorLevel&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(Configuration)' == 'Release'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Info&lt;span class="nt"&gt;&amp;lt;/SolutionPackageErrorLevel&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;SolutionPackageEnableLocalization&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/SolutionPackageEnableLocalization&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- The value for this property has to be passed during runtime when you run dotnet build --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;SolutionFileVersion&amp;gt;&amp;lt;/SolutionFileVersion&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;SolutionPackageZipFilePath&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(SolutionFileVersion)' != '' AND '$(Configuration)' == 'Debug'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    $(OutputPath)$(MSBuildProjectName)_v$(SolutionFileVersion)_unmanaged.zip&lt;span class="nt"&gt;&amp;lt;/SolutionPackageZipFilePath&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;SolutionPackageZipFilePath&lt;/span&gt; &lt;span class="na"&gt;Condition=&lt;/span&gt;&lt;span class="s"&gt;"'$(SolutionFileVersion)' != '' AND '$(Configuration)' == 'Release'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    $(OutputPath)$(MSBuildProjectName)_v$(SolutionFileVersion)_managed.zip&lt;span class="nt"&gt;&amp;lt;/SolutionPackageZipFilePath&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Points the solution build to use &lt;code&gt;map.build.xml&lt;/code&gt; (which includes the plugin package mapping)&lt;/li&gt;
&lt;li&gt;Sets up different package types and error levels for Debug vs Release builds&lt;/li&gt;
&lt;li&gt;Enables localization support&lt;/li&gt;
&lt;li&gt;Configures versioned output file naming for solution packages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this setup, when you build the solution project (&lt;code&gt;dotnet build&lt;/code&gt; or MSBuild), it will properly locate the plugin package via the &lt;code&gt;FileToFile&lt;/code&gt; mapping in &lt;code&gt;map.build.xml&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 6. Automation Script (Sync + Cleanup)
&lt;/h2&gt;

&lt;p&gt;To be sure to always perform step 1 and 2 described above, I tend to use a special-purpose powershell script.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sync.ps1&lt;/code&gt; (proposed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$SolutionFolder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$SyncMap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SolutionFolder&lt;/span&gt;&lt;span class="s2"&gt;/map.sync.xml"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[Sync] Using map: &lt;/span&gt;&lt;span class="nv"&gt;$SyncMap&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;solution&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--map&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SyncMap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-loc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;throw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Solution sync failed"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Find and remove all .nupkg files under the solution folder&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$nupkgFiles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SolutionFolder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*.nupkg"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-File&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$nupkgFiles&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-gt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[Cleanup] Found &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$nupkgFiles&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; .nupkg file(s) to remove:"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$nupkgFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"  - &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[Cleanup] All .nupkg files removed from solution folder."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[Cleanup] No .nupkg files found in solution folder (already clean)."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ⚖️ 8. Pros &amp;amp; Cons of Dual-Map Strategy
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Unblocks daily sync workflow immediately.&lt;/li&gt;
&lt;li&gt;Keeps repository clean of large binary artifacts.&lt;/li&gt;
&lt;li&gt;Build remains deterministic (stable filename always present).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Two mapping files add cognitive overhead.&lt;/li&gt;
&lt;li&gt;Extra cleanup step (script) until the underlying issue is fixed.&lt;/li&gt;
&lt;li&gt;Risk of accidental use of the build map during sync (document clearly in README / contributor guide).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ⚠️ Disclaimer
&lt;/h2&gt;

&lt;p&gt;If/when the upstream behavior changes, this post can be revisited to retire the workaround.&lt;/p&gt;

&lt;p&gt;Feel free to adapt the script &amp;amp; structure to your own Dataverse ALM conventions.&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>powerapps</category>
      <category>powerfuldevs</category>
    </item>
  </channel>
</rss>
