DEV Community

Cover image for Plain SDK vs a Plugin Framework: When Does the Abstraction Pay Off?
Ján
Ján

Posted on

Plain SDK vs a Plugin Framework: When Does the Abstraction Pay Off?

The Dataverse SDK is not something you graduate from. A framework only earns its place when it removes recurring engineering cost without hiding the platform you still need to understand.

A Dataverse plug-in can be very small.

You implement IPlugin, read the execution context, do the work, register the step, and move on.

For a single internal plug-in, that can be exactly the right solution.

The interesting question starts later.

What happens when one plug-in becomes twenty? When multiple developers touch the same solution? When DEV, TEST and PROD no longer have exactly the same registrations? When every new plug-in starts by copying the same context, tracing, validation and registration patterns?

That is where teams usually start asking whether they need a framework.

The useful question is not:

Should every Dataverse project use a plug-in framework?

It is:

At what point does the cost of repeating the same engineering decisions become higher than the cost of adopting an abstraction?

That is what this article is about.

TL;DR

  • Plain SDK is a valid architecture. A small, stable plug-in does not need a framework just because one exists.
  • Frameworks start paying for themselves when repetition becomes delivery risk — more developers, more environments, more registrations, longer solution lifetime and a stronger need for repeatable tests.
  • The real decision is who owns the conventions the SDK leaves open: each developer, your internal standards, or a framework.

What the Plain SDK gives you in 2026

The standard Dataverse plug-in model is still the foundation.

A plug-in is a class that implements IPlugin and executes because a registered Dataverse step connects that class to an event in the Dataverse execution pipeline.

Microsoft's supported model still revolves around:

  • IPlugin,
  • IPluginExecutionContext,
  • IOrganizationService,
  • plug-in assemblies and packages,
  • message processing steps,
  • images,
  • filtering attributes,
  • the Plug-in Registration Tool,
  • Power Platform CLI,
  • and Power Platform Tools for Visual Studio.

The first-party tooling around the SDK has improved considerably.

Power Platform CLI can initialize a plug-in project:

pac plugin init
Enter fullscreen mode Exit fullscreen mode

and can import a plug-in assembly or package into Dataverse:

pac plugin push
Enter fullscreen mode Exit fullscreen mode

The Plug-in Registration Tool is still supported and can be launched through:

pac tool prt
Enter fullscreen mode Exit fullscreen mode

Power Platform Tools for Visual Studio can create, register, deploy, debug and profile plug-ins as well.

So the reason to adopt a framework in 2026 is not that Microsoft gives you no tooling.

It is also important to separate two concerns:

Getting plug-in code into Dataverse
              ≠
Defining exactly when and how it executes
Enter fullscreen mode Exit fullscreen mode

An assembly can be present while the registration around it is still wrong.

That distinction becomes increasingly important as a solution grows.


A small plug-in may not need a framework

Imagine a plug-in with one responsibility:

On Account update, reject a change if a specific business condition is not satisfied.

A straightforward SDK implementation may be entirely reasonable:

public class ValidateAccountPlugin : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        var context =
            (IPluginExecutionContext)serviceProvider
                .GetService(typeof(IPluginExecutionContext));

        if (!context.InputParameters.TryGetValue("Target", out var targetValue))
            return;

        if (targetValue is not Entity target)
            return;

        if (target.LogicalName != "account")
            return;

        if (!target.Contains("name"))
            throw new InvalidPluginExecutionException(
                "Account name is required.");
    }
}
Enter fullscreen mode Exit fullscreen mode

There is nothing inherently wrong with this.

For a tiny project, adding another dependency, another execution model and another deployment convention may create more complexity than it removes.

Zero framework is a valid architecture.

A framework should have to justify itself.


Where the cost starts to appear

The SDK gives you platform primitives.

It deliberately does not prescribe the architecture of your whole solution.

That freedom is useful, but the team has to provide the missing discipline.

As the solution grows, the same categories of work tend to repeat.

Execution boilerplate

Every plug-in needs some combination of:

  • execution context,
  • target handling,
  • pre/post images,
  • organization services,
  • tracing,
  • configuration,
  • stage/message checks,
  • error handling.

A good team can centralize this themselves.

A less disciplined codebase gradually gets twenty slightly different versions of the same thing.

Validation mixed with execution

The SDK does not force you to separate:

Can this operation run?
Enter fullscreen mode Exit fullscreen mode

from:

What should this operation do?
Enter fullscreen mode Exit fullscreen mode

That separation is an architectural decision.

Without a convention, plug-in classes often accumulate both.

Registration lives somewhere else

The business code can be perfectly version controlled while the actual plug-in step still exists only as a Dataverse record.

That means the complete behavior is:

code + deployed registration
Enter fullscreen mode Exit fullscreen mode

not just:

code
Enter fullscreen mode Exit fullscreen mode

If registration is not reproducible, source control is only telling part of the story.

Environment drift

A common operational problem is not a broken DLL.

It is an environment that contains:

  • an old step,
  • a disabled step,
  • different filtering attributes,
  • a missing image,
  • a different execution order,
  • or a duplicate registration.

The application can compile successfully while DEV and PROD still behave differently.

Testing becomes inconsistent

Plain SDK code can absolutely be tested.

The SDK does not make code untestable.

But the team has to decide:

  • what is isolated from the plug-in entry point,
  • what gets mocked,
  • whether pipeline behavior is simulated,
  • whether anything is tested against real Dataverse,
  • how test data is created and removed.

Again: freedom means the team owns the convention.


Five signals that an abstraction may start paying for itself

There is no plug-in count at which a framework suddenly becomes mandatory.

But there are useful signals.

1. You are repeating the same structure

If every new plug-in starts by copying another plug-in and deleting half of it, you already have an implicit framework.

It just lives in copy-paste.

At that point, formalizing the pattern can reduce variance.

2. Registration is part of delivery risk

If deployment requires someone to open the Plug-in Registration Tool and verify steps manually, registration has become part of your delivery process.

For a small solution, that may be acceptable.

For a team shipping frequently across several environments, it becomes an operational dependency.

3. The project needs repeatable tests

The value of reusable test infrastructure increases with the lifetime of the solution.

A plug-in that changes once a year can survive manual verification.

A business-critical implementation that changes every sprint benefits much more from repeatable regression tests.

4. More developers need to understand the same codebase

Frameworks can reduce local creativity.

That sounds negative, but in a large codebase it is often useful.

If every plug-in has the same shape, a developer can move between features without first reverse-engineering the author's preferred pattern.

5. The solution will live for years

The longer a solution lives, the more expensive inconsistency becomes.

The first plug-in is cheap.

The fiftieth deployment, third developer and fourth environment are where architectural decisions start compounding.


What a framework should actually buy you

A framework should not be measured by how many classes it adds.

It should be measured by which recurring problems disappear.

For a Dataverse plug-in project, useful framework responsibilities can include:

Problem Plain SDK What a framework can add
Plug-in execution Platform primitives Consistent execution pipeline
Validation Team convention Explicit validation lifecycle
Step registration PRT / VS tooling / custom automation Registration defined in code
Environment consistency Team/CI responsibility Deterministic registration synchronization
Diagnostics ITracingService + custom patterns Standard structured logging
Testing Build your own approach Reusable unit/integration test foundation
Project structure Team decision Consistent templates and conventions
Onboarding Learn project-specific patterns Predictable project shape

A framework does not remove the SDK.

It organizes how the team uses it.

There is also an emerging AI-assisted development benefit to making those conventions explicit. Small task-sized units, code-defined registration and executable tests give coding agents clearer boundaries and feedback than a large plug-in class whose behavior has to be inferred. Pillaro explicitly treats this as part of its AI-Ready Standard — not as autonomous development magic, but as a way to make the codebase easier for both developers and AI tools to reason about.


A concrete example: the Pillaro approach

Pillaro is one possible answer to these problems.

Disclosure: I maintain it.

It is open source under Apache-2.0, free to use in commercial projects, and production-proven. The framework's first production adopter was Seyfor, where it has been deployed in enterprise Dynamics 365 and Power Platform solutions.

Starting a new solution does not have to mean wiring the framework, testing package and early-bound tooling by hand. Pillaro ships official starter templates for both dotnet new (CLI, VS Code, Visual Studio) and a Visual Studio VSIX template, generated from the same shared source so both produce an identical project structure.

For the fastest path to a running task-based plug-in project:

dotnet new install Pillaro.Dataverse.PluginTemplate.DotNetNew
dotnet new pillaro-dataverse-plugin-dotnet -n MySolution
Enter fullscreen mode Exit fullscreen mode

That is often the practical answer to "I just need one plug-in fast" — you get the task/validation structure, code-defined registration, testing package and early-bound tooling already wired, instead of assembling them by hand.

Pillaro is also listed in Microsoft Learn's Community Tools for Microsoft Dataverse catalog and is published in Microsoft Marketplace. The Marketplace offer installs the Dataverse-side runtime used by the framework, while the C# framework itself is consumed as a NuGet package.

Microsoft Learn explicitly notes that community tools are supported by their publishers rather than by Microsoft.

Microsoft Learn listing · Microsoft Marketplace

The framework uses a task-based model where validation and execution are explicit phases:

Pillaro runtime deployment diagram

A task follows a consistent lifecycle:

public class MyFirstTask(
    IServiceProvider serviceProvider,
    TaskContext taskContext)
    : TaskBase<Logic.Task>(serviceProvider, taskContext)
{
    protected override ICompleteValidation AddValidations(
        IBasicModeValidation validator)
    {
        return validator
            .WithMode(PluginMode.Synchronous)
            .WithStage(PluginStage.Preoperation)
            .WithMessages(["Create"])
            .ForEntity(ContextEntity.LogicalName);
    }

    protected override void DoExecute()
    {
        // Business logic
    }
}
Enter fullscreen mode Exit fullscreen mode

The plug-in then registers that task for runtime execution:

RegisterTask<MyFirstTask>(
    PluginStage.Preoperation,
    ["Create"],
    Task.EntityLogicalName,
    PluginMode.Synchronous);
Enter fullscreen mode Exit fullscreen mode

Deployment metadata is declared separately:

public override void Register(IPluginRegistration registration)
{
    registration
        .OnCreate<Task>("8c46d6e6-3c25-4b9d-9264-6c0d02b4d2f1")
        .PreOperation()
        .Synchronous()
        .Rank(1);
}
Enter fullscreen mode Exit fullscreen mode

That separation is intentional.

RegisterTask(...) tells the runtime which task to execute.

Register(IPluginRegistration registration) tells deployment tooling which Dataverse registration should exist.

The stable step ID gives the deployment model a persistent identity for the step, while the deployment tooling synchronizes the declared registration with Dataverse.

Pillaro also provides functional testing infrastructure that runs against a real Dataverse environment with deployed plug-ins and cleans up tracked test data afterwards.

That is one design choice.

Other frameworks make different choices.

The point is not that this is the only valid architecture. It is an example of what it means for a framework to own repeated conventions on purpose.


What a framework should not do

A framework can solve real problems and still be the wrong choice.

There are several failure modes worth watching for.

It should not hide Dataverse from you

A developer using a framework still needs to understand:

  • pipeline stages,
  • execution depth,
  • transactions,
  • images,
  • filtering attributes,
  • synchronous vs asynchronous execution,
  • recursion,
  • security context,
  • service calls.

If the abstraction makes those concepts invisible, debugging becomes harder rather than easier.

It should not own everything just because it can

In 2026, Microsoft already provides first-party tooling for large parts of the development lifecycle.

For example:

Plug-in project      → pac plugin
Typed model code     → pac modelbuilder
Solution lifecycle   → pac solution
PRT                  → pac tool prt
Enter fullscreen mode Exit fullscreen mode

A framework does not need to reimplement every Power Platform CLI feature.

Sometimes the better design is to integrate with the Microsoft tool instead of competing with it.

Pillaro follows that approach for early-bound generation: its project tooling prepares a local Tools/EarlyBound/ workflow, but the actual generator underneath is Microsoft's pac modelbuilder.

It should not make leaving impossible

A healthy abstraction sits on top of understandable platform concepts.

You should be able to explain what the framework is doing in Dataverse without treating it as magic.

That keeps migration possible and reduces long-term dependency risk.


When Plain SDK is probably the better choice

There are situations where I would deliberately stay with the SDK.

One or two small plug-ins

If the entire solution contains a few simple steps, there may be no recurring cost worth abstracting.

You are learning Dataverse plug-in execution

Learning the raw SDK first is useful.

Understanding IPluginExecutionContext before wrapping it gives you a much better mental model when something eventually goes wrong.

Third-party dependencies are restricted

Some customers or regulated environments do not allow additional runtime libraries without a review process.

The plain SDK may be the simplest acceptable option.

The existing codebase is stable

If a mature project has working deployment, documented conventions and little ongoing development, introducing a framework can create migration cost without enough return.

Your architecture is already standardized internally

A strong engineering team may already have:

  • a plug-in base,
  • registration automation,
  • test infrastructure,
  • logging,
  • templates,
  • CI/CD.

At that point you may already have the useful parts of a framework — just internally.

Replacing them with a public framework is not automatically an improvement.


When a framework becomes easier to justify

The case gets stronger when several of these are true:

  • many plug-in steps are under active development,
  • several developers work in the same solution,
  • deployment spans multiple environments,
  • registration drift has already caused incidents,
  • business logic is expected to evolve for years,
  • automated regression testing matters,
  • onboarding speed matters,
  • the same architecture is reused across multiple customer projects.

Notice what is missing from that list:

"The plug-in has a lot of lines of code."

Line count is not the main issue.

Repeated engineering decisions are.


The decision I would make

For a small, isolated plug-in, I would start with the SDK.

For a long-lived solution, I would make one explicit decision early:

Who owns the conventions that the SDK intentionally leaves open?

There are three reasonable answers:

1. The individual developer
2. The team / internal standards
3. A framework
Enter fullscreen mode Exit fullscreen mode

The dangerous answer is:

Nobody — we'll decide separately every time.
Enter fullscreen mode Exit fullscreen mode

That is where accidental architecture starts.


A practical decision checklist

Before adding a framework, ask:

  • What recurring problem are we solving?
  • Can Microsoft tooling already solve it?
  • Can a small internal convention solve it?
  • Does the framework make deployment more reproducible?
  • Does it make plug-in behavior easier to understand?
  • Does it improve testability in the way we actually test?
  • Will a new developer understand the architecture faster?
  • Can we still reason about the underlying Dataverse execution model?
  • What happens if the framework is no longer maintained?
  • Is the adoption cost lower than the repetition cost over the expected lifetime of the solution?

If those questions have good answers, the abstraction may pay for itself.

If they do not, the SDK is still there — and it is still a perfectly valid choice.


What comes next

In Part 3, I will compare two actively maintained frameworks with very different design centers:

Pillaro and XrmFramework.

Not to pick a winner, but to look at what each one chooses to own: execution structure, typed models, deployment, debugging, testing and developer tooling.

If you want to try the approach on a real project, Pillaro is Apache-2.0 licensed and free for commercial use. The GitHub repository contains the getting-started guide, NuGet packages, templates, deployment documentation and testing stack. It is already used in production, with Seyfor documented as the first production adopter.


Sources


Disclosure: I maintain Pillaro. The goal of this article is not to argue that every Dataverse project needs a framework. It is to make the trade-off explicit.

Top comments (0)