A solutions architect on a mid-size D365 F&O implementation recently ran into a problem that had nothing to do with business logic. Her team had finally moved their plugin assemblies into proper NuGet-based Dataverse plugin packages, the newer code-first model that the Power Platform CLI now supports directly. The build worked. The package generated correctly. And then the pipeline stalled, because nobody could answer a simple question: how do you tell Azure DevOps to actually deploy the thing you just built, when Microsoft’s own build tools extension has no task for it?
That gap is worth understanding in detail, because it sits at the exact point where Dataverse plugin development is genuinely improving and where its ALM tooling has not caught up.

From Manual Registration to Code-First Packages
For most of the last decade, deploying a Dataverse plugin meant compiling a class library in Visual Studio, opening the Plugin Registration Tool, and manually selecting an assembly to register or update. It worked, but it kept plugin deployment outside the discipline that governs everything else in a mature ALM pipeline: predictable artifacts, versioned builds, and a repeatable path from commit to environment.
The Power Platform CLI changes that equation with two commands: pac plugin init, which scaffolds a plug-in class library project, and pac plugin push, which imports the resulting artifact into Dataverse. The output of a build is no longer a loose assembly file. It is a NuGet package, generated automatically in the bin\outputPackages folder on every build, containing the plugin assembly and its dependencies bundled together. That single change, treating the plugin as a proper package rather than a file you happen to compile, is what makes plugin code finally look like the rest of a modern .NET pipeline.
What Dataverse Plugin Packages Actually Solve
The practical win is dependency handling. Under the old model, any third-party or shared library your plugin referenced had to be merged into the assembly or registered separately, and both approaches created their own headaches around versioning and duplication across solutions. A plugin package bundles dependent assemblies inside the same NuGet artifact, so the whole thing deploys and updates as one unit.
Signing works differently here too, and it is worth getting the policy decision right early rather than discovering it mid-project. Microsoft’s own guidance is explicit that signing is all-or-nothing: if the primary plugin assembly is strong-name signed, every dependent assembly in the package must be signed as well. Most teams find this impractical once they start pulling in third-party libraries they do not control, which is why pac plugin init exposes a --skip-signing flag, and why it has effectively become the default choice for anyone using dependent assemblies rather than a single self-contained plugin.
The Immutability Trap
Here is where the model’s real character shows up, and where a team can get burned if nobody flags it in advance. Once a plugin package is created in Dataverse, its name and version are fixed. An attempt to change either afterward, including through the API, simply fails. Every build produces a package with a new version embedded in its filename, which means there is no such thing as “redeploy the same package again.” Each build is a new, uniquely identified artifact from Dataverse’s point of view, whether you intended that or not.
That has two consequences that matter for pipeline design. First, if a plugin update removes an assembly or a type that is still referenced by an active plugin step registration, Dataverse rejects the update outright. You have to go remove or repoint those step registrations first, then update the package, not the other way around. Second, you cannot unregister a package at all while any of its assemblies still have active step registrations attached to them. Both rules are sensible from a data-integrity standpoint, but they mean plugin retirement has to be sequenced deliberately: deregister the steps, confirm nothing else depends on the assembly, and only then remove or replace the package.
One independent practitioner account, documented on the .NET Dust blog in 2024, captured how disruptive the versioning behavior can be during ordinary iterative development, well before any pipeline is involved. Because pac plugin push has no built-in notion of “the current build,” the developer had to script around it: an MSBuild target increments the file version before compilation, a post-build event persists that value into the project file through a small helper utility, the NuGet package picks up the new version automatically, and only then does an automated pac CLI call push the exact resulting filename. What should be a single command turned into a four-step workaround, built by one engineer, to get a repeatable local deployment loop. That is a reasonable thing for an individual developer to solve on their own machine. It is not something you want reinvented independently by every team building a Dataverse ALM pipeline.
The Build Tools Gap
That last point is the one worth taking to whoever owns your ALM strategy. Microsoft’s Power Platform Build Tools extension, the standard toolkit for Dataverse pipelines in Azure DevOps and GitHub Actions, covers an extensive set of operations: importing, exporting, packing, and unpacking solutions, applying upgrades, managing environments, running the Power Platform Checker, and even a preview set of catalog-submission tasks. Reviewing the current task reference, plugin packages and pac plugin push are not among them. There is no first-class, Microsoft-supported pipeline task for deploying a plugin package, in contrast to the mature tooling that already exists for solutions.
In practice, that means every team adopting code-first plugin packages has to write its own pipeline stage: typically a script task that installs a pinned version of the PAC CLI, authenticates against the target environment (a service principal, not a personal login, for anything beyond a developer sandbox), locates the build output, and issues the push. None of that is exotic engineering. It is, however, unsupported custom scripting sitting next to a set of Microsoft-maintained tasks that get security updates, documentation, and predictable behavior across CLI versions. That asymmetry is easy to miss until a CLI update quietly changes an argument name and a script that nobody has looked at in eight months starts failing in production.

Building This Into an ALM Pipeline Properly
A few decisions are worth making explicit rather than leaving to whichever developer happens to write the first plugin deployment script. Pin the PAC CLI version in the pipeline definition itself, not just on developer machines, so a CLI upgrade cannot silently change push behavior in production. Treat the plugin push step as its own reviewed pipeline stage with the same environment gating and approval flow as solution deployment, rather than a script someone bolted on during a sprint to unblock a release. Decide the assembly-signing policy at the team or center-of-excellence level, since the all-or-nothing signing rule effectively locks in a convention for every future package once dependent assemblies enter the picture.
Sequence retirements carefully. Before removing or consolidating an old plugin package, confirm which step registrations still point at it and clear those first, since Dataverse will otherwise block the change regardless of what your pipeline script assumes should happen. And build the versioning logic into the pipeline itself, following the pattern the .NET Dust workaround demonstrated, rather than trusting a developer’s local environment to produce a consistently versioned artifact that a shared pipeline can then find and deploy.
None of this makes the code-first plugin package model a bad idea. It is a genuine improvement over registering assemblies by hand through a desktop tool, and it brings plugin code closer to how the rest of a .NET codebase is built and shipped. The catch is that Microsoft shipped the authoring half of that story before the deployment half, and the gap between them is exactly where implementation teams are currently improvising. Firms that have already built Dataverse ALM pipelines from the ground up, Routeget Technologies among them, have generally closed that gap with a small, explicitly maintained script stage rather than an ad hoc one, which is the difference between a pipeline that survives a CLI version bump and one that quietly breaks the next time someone updates it.
#Dataverse #DataversePlugins #PowerPlatformALM #AzureDevOps #ExtensibilityAndProDev #EnterpriseArchitecture
No comment yet, add your voice below!