<?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>ALM on Power Platform: ADO + GitHub, the best of both worlds</title>
      <dc:creator>Riccardo Gregori</dc:creator>
      <pubDate>Mon, 04 May 2026 08:50:02 +0000</pubDate>
      <link>https://forem.com/_neronotte/alm-on-power-platform-ado-github-the-best-of-both-worlds-5dbh</link>
      <guid>https://forem.com/_neronotte/alm-on-power-platform-ado-github-the-best-of-both-worlds-5dbh</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;You can integrate Azure DevOps with GitHub to get the best of both worlds in Power Platform development.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ADO stays as the backbone&lt;/strong&gt;: work items, sprint planning, test plans, and deploy pipelines all remain on Azure DevOps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code moves to GitHub&lt;/strong&gt;: &lt;em&gt;Power App Code Apps&lt;/em&gt; or &lt;em&gt;Power Pages SPA&lt;/em&gt; live in GitHub repos, unlocking native GitHub Copilot integration and the Copilot Cloud Agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The two platforms are linked&lt;/strong&gt;: commits reference ADO work items via &lt;code&gt;AB#{id}&lt;/code&gt;, creating a bidirectional traceability layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Copilot Cloud Agent works autonomously&lt;/strong&gt;: assign it a task, it opens a PR; assign it to a PR, it reviews and fixes issues — all guided by your repo's own instructions.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Managing ALM on Power Platform&lt;/strong&gt; has never been straightforward. Anyone who works seriously with CRM and business applications knows it: an application's lifecycle doesn't end when the code is written. &lt;em&gt;Requirements, sprints, pipelines, tests, reviews&lt;/em&gt; — everything needs to fit together coherently. And for years, my answer to that problem has been Azure DevOps.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏗️ The starting point: Azure DevOps as the backbone
&lt;/h2&gt;

&lt;p&gt;Azure DevOps has always given me what I needed: Work Items for tracking requirements, Git repositories, CI/CD pipelines, integrated test plans. A complete ecosystem built for teams that want full control over the application lifecycle — from user stories all the way to production deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Power Platform applications ADO remains a rock-solid choice&lt;/strong&gt;, whether you're building &lt;em&gt;canvas apps, model-driven apps, or Power Pages portals&lt;/em&gt;. Solution pipelines, ALM tooling like Power Platform Build Tools, and native environment integration make the workflow reproducible and governable.&lt;/p&gt;

&lt;p&gt;All good. &lt;/p&gt;

&lt;p&gt;Then came the &lt;strong&gt;Power App Code Apps&lt;/strong&gt; and &lt;strong&gt;Power Pages SPA&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎮 New App types are changing the rules of the game
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Power App Code Apps&lt;/strong&gt; and &lt;strong&gt;Power Pages SPAs&lt;/strong&gt; represent a paradigm shift: &lt;strong&gt;you're no longer working with a low-code designer&lt;/strong&gt;, you're writing real code — TypeScript, React, custom components. And when you write real code, &lt;strong&gt;AI in the development cycle stops being a luxury and becomes a genuine accelerator&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They benefit enormously from AI in their development cycle&lt;/strong&gt;: code completion, automated review, test generation. This changes the weight of your platform choice significantly.&lt;/p&gt;

&lt;p&gt;By company policy, our trusted assistant is &lt;strong&gt;GitHub Copilot&lt;/strong&gt;. And here's where the first point of friction emerges: &lt;strong&gt;GitHub Copilot lives on GitHub&lt;/strong&gt;, not on ADO.&lt;/p&gt;

&lt;h2&gt;
  
  
  💎 GitHub is more than just a repository
&lt;/h2&gt;

&lt;p&gt;In recent months, with the introduction of the GitHub Copilot Cloud Agent, the platform has taken a significant leap forward. GitHub is &lt;strong&gt;no longer a mere Git repo storage: it's an autonomous agent platform&lt;/strong&gt;, that is able to run agents in the cloud, in the background, like real collaborators.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;GitHub doesn't match ADO's completeness for the rest of the lifecycle&lt;/strong&gt;: requirements management, sprint planning, structured test plans — what an enterprise team needs to govern a complex project remains ADO's strong suit.&lt;/p&gt;

&lt;p&gt;The question I asked myself was: &lt;em&gt;can I get the best of both?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 The hybrid setup: ADO + GitHub integrated
&lt;/h2&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%2Ft8osbrqado1r7rvrrbmw.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%2Ft8osbrqado1r7rvrrbmw.png" alt="ALM Architecture" width="681" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The answer is yes — and the setup is less complicated than it might sound. &lt;a href="https://learn.microsoft.com/en-us/azure/devops/cross-service/github-integration?view=azure-devops" rel="noopener noreferrer"&gt;Azure DevOps and GitHub integrate natively&lt;/a&gt;. Once the connection between the two environments is configured, the two systems start talking to each other bidirectionally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code lives on GitHub&lt;/strong&gt;. &lt;strong&gt;Work items, requirements, sprint planning, test plans — everything stays on ADO&lt;/strong&gt;. &lt;strong&gt;Deployment pipelines to Power Platform keep running on ADO Pipelines&lt;/strong&gt;, with the connectors you already know.&lt;/p&gt;

&lt;h2&gt;
  
  
  ↔️ The bidirectional link in commits
&lt;/h2&gt;

&lt;p&gt;One of the things I cared most about was &lt;strong&gt;not losing the connection between code and work items&lt;/strong&gt;. In pure ADO, this works naturally: a commit can reference a work item and build a traceable link. I wanted the same result in the hybrid setup.&lt;/p&gt;

&lt;p&gt;And I have it. The syntax to use in commit messages (or PR comments) is straightforward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In GitHub commits, use the &lt;code&gt;AB#{WORKITEMID}&lt;/code&gt; syntax to automatically link the commit to the corresponding work item on Azure Boards. The link appears on both sides, bidirectionally.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Writing &lt;code&gt;fix: error handling on component init AB#1234&lt;/code&gt; in the commit message is enough to create a traceable link between the code change and the ADO task. The integration &lt;strong&gt;automatically populates the "Development" section with a reference to the commit&lt;/strong&gt; or PR.&lt;/p&gt;

&lt;p&gt;Anyone who works with ADO knows how important this kind of traceability is during audits, release planning, or simply when trying to understand "why was this code written this way."&lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 The Copilot Cloud Agent: a collaborator that works while you sleep
&lt;/h2&gt;

&lt;p&gt;This is the part that &lt;strong&gt;genuinely changed how I work&lt;/strong&gt;. The &lt;strong&gt;GitHub Copilot Cloud Agent&lt;/strong&gt; can be &lt;strong&gt;assigned directly to a bug or a task&lt;/strong&gt; — and from that point, &lt;strong&gt;it starts working on it autonomously&lt;/strong&gt;, opening a Pull Request and building the solution in the background.&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%2F0jzonp2a9ww91i8u6fbs.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%2F0jzonp2a9ww91i8u6fbs.png" alt="Assign a work item to GitHub Copilot Cloud Agent" width="613" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The quality of the output depends heavily on &lt;strong&gt;how you write the task&lt;/strong&gt;. A well-structured issue — with clear context, acceptance criteria, and references to the existing codebase — produces a significantly better PR than a vague, one-line title. The investment you make when writing the task pays off when the agent delivers the work done.&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%2Ful6lr9qp9doht0dr92vk.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%2Ful6lr9qp9doht0dr92vk.png" alt="Activities performed by GH Copilot Cloud Agent" width="681" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But the most compelling part is the &lt;strong&gt;automated Pull Request review&lt;/strong&gt;. The Copilot Cloud Agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;analyzes every PR opened by developers, &lt;/li&gt;
&lt;li&gt;anticipates problems, &lt;/li&gt;
&lt;li&gt;flags questionable patterns, &lt;/li&gt;
&lt;li&gt;and can be actively engaged to fix them — all inside the PR, in comments, in a collaborative way.&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%2Fr89zvck7g1n6nqiyftex.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%2Fr89zvck7g1n6nqiyftex.png" alt="Pull Request reviewed by GitHub Copilot" width="800" height="373"&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%2Feeoxrykbvhpdbe5sfh12.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%2Feeoxrykbvhpdbe5sfh12.png" alt="Pull request fixed by GitHub Copilot" width="800" height="1100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Contextual review, not generic feedback
&lt;/h2&gt;

&lt;p&gt;There's one aspect that sets this configuration apart from a generic "AI that does code review": &lt;strong&gt;the Copilot Cloud Agent works with the repository's context&lt;/strong&gt;. This means its &lt;strong&gt;reviews aren't generic&lt;/strong&gt; — they're &lt;strong&gt;grounded in the guidelines, architectural patterns, and conventions the team has defined in the repository itself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Through instruction files, skills, and rules configurable at the repository level (&lt;code&gt;.github/copilot-instructions.md&lt;/code&gt; and custom instructions), you can teach the agent how your Code App is structured, which patterns to follow, which anti-patterns to avoid, and which naming conventions to enforce.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;.github/copilot-instructions.md&lt;/code&gt; file is your control point: here you define architectural guidelines, team conventions, and patterns to follow. The agent uses them as a reference for every review and every task you assign to it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The result is a review that speaks your project's language — not a generic checklist that anyone could copy from a blog post (including this one). If the team has decided that components follow a certain error-handling pattern, the agent knows it — and verifies it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The ADO + GitHub setup isn't a compromise: it's a deliberate choice that leverages the strengths of each platform in the domain where it excels. ADO for governance, planning, and the full lifecycle. GitHub for development, AI, and the Copilot Agent's capabilities.&lt;/p&gt;

&lt;p&gt;For teams working on Power App Code Apps — and increasingly on Power Pages SPAs too — this configuration reduces the cost of review, improves the quality of the code produced, and maintains the traceability that an enterprise project demands.&lt;/p&gt;

&lt;p&gt;The Copilot Cloud Agent doesn't replace the developer: it anticipates problems, handles the more mechanical tasks, and frees up developers' time for the decisions that matter. And that's exactly what you'd ask of a good collaborator.&lt;/p&gt;

&lt;p&gt;One frontier I'm actively looking forward to exploring: &lt;strong&gt;bringing the same degree of automation to model-driven apps&lt;/strong&gt;. Today, the code-first nature of Code Apps is what makes the Copilot Agent so effective — but &lt;strong&gt;with tools like PACX, App Maker MCP Server, Dataverse MCP Server, Power Platform Skills for Generative Pages, and so on&lt;/strong&gt;, the prospect of applying a similar AI-driven workflow to model-driven development is becoming increasingly concrete. Watch this space.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>powerplatform</category>
      <category>powerapps</category>
      <category>githubcopilot</category>
    </item>
    <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>
  </channel>
</rss>
