Flectic
Microsoft Power Platform · DeveloperDynamics 365

Dataverse for Developers & Application Lifecycle Management

Dataverse development is metadata-driven ALM: you program against the Organization service (ServiceClient / Web API), ship changes as solutions (unmanaged in dev, managed downstream), and promote with the Power Platform CLI, native Git integration, or CI/CD pipelines. This guide covers the SDK client, early-bound vs late-bound code, solution layers and publishers, the event pipeline and plug-ins (including the PluginBase pattern), connection references and environment variables, Dataverse Git integration for maker-friendly source control, and how to choose among Power Platform Pipelines, Azure DevOps, and GitHub Actions for production releases.

13 min readUpdated Aug 3, 202618 sources cited

TL;DR — Key takeaways

  • Metadata-driven: tables, columns, and relationships are themselves rows in the platform's schema tables, queryable at runtime
  • ServiceClient (Microsoft.PowerPlatform.Dataverse.Client) is the primary .NET client; it implements IOrganizationService and handles auth + retry
  • A solution is the atomic deployable unit — it carries components and their dependency graph, imported in order
  • pac is cross-platform and the single entry point for auth, admin, solution, pcf, plugin, canvas, and connector operations
01Foundations

The developer mental model: metadata is the schema

The single biggest mental shift for developers coming to Dataverse from a traditional database background is that the schema is not a script you run — it is metadata. When a maker creates a table or adds a column in the Power Apps maker portal, they are not running a CREATE TABLE; they are writing rows into the platform's own metadata tables (Entity, Attribute, Relationship). Microsoft's developer documentation frames it directly: Dataverse is a metadata-driven platform where 'all the data about the data' is itself stored and queryable at runtime, and your code reads that metadata to know what columns exist, what types they are, and how they relate. This is why a model-driven app can regenerate its forms and views automatically when you change a column — the UI is generated from the same metadata your plug-in reads.

The practical consequence is that you program against the Organization service, not a connection string. There is no SqlClient, no hand-written joins, and no stored procedures in the normal flow. Instead, every operation — create, update, delete, retrieve, and richer business messages — is a request against a typed message pipeline. The same Organization service is what the SDK for .NET, the OData v4 Web API, Power Automate flows, and even the model-driven app UI ultimately call. A developer who internalizes 'everything is a message against a metadata table' understands 80% of why Dataverse behaves the way it does, from cascading deletes to service-protection throttling.

A second consequence is that customization and code live together. A business rule, a calculated column, a security role, a model-driven form, a C# plug-in, and a Power Automate flow are all first-class solution components that ship in the same solution and are versioned together. There is no clean split between 'the database team' and 'the app team' — the schema, the logic, and the presentation are one deployable unit. This is enormously productive when embraced, and a source of painful merge conflicts when teams try to edit the same unmanaged solution in a shared development environment without source control.

  • Metadata-driven: tables, columns, and relationships are themselves rows in the platform's schema tables, queryable at runtime
  • You program against the Organization service, not SQL — there is no SqlClient in the normal flow
  • Every operation is a typed message against a pipeline shared by the SDK, the Web API, flows, and the model-driven UI
  • Schema, logic, and presentation are all solution components versioned and shipped together
02Data access

Reading and writing data from code: ServiceClient and the Organization service

From .NET code, the primary client is the ServiceClient class in the Microsoft.PowerPlatform.Dataverse.Client namespace (the successor to the older CrmServiceClient). ServiceClient implements IOrganizationService — the interface at the heart of the SDK — and adds connection management, retry-on-429 throttling, batched requests, and support for several authentication methods against Microsoft Entra ID (OAuth, client secret, and client certificate for unattended automation). For most server-side work — migration tools, integrations, console apps, Azure Functions — ServiceClient is the class you instantiate once and reuse for the lifetime of the process, because it manages the underlying authenticated channel and respects the service-protection API limits.

Two coding styles sit on top of IOrganizationService, and choosing between them is a recurring decision. The late-bound style uses the generic Entity class and string keys — you write new Entity("account") and access columns with entity.GetAttributeValue<string>("name"). It is loosely typed, resilient to schema changes, and the natural choice for generic tooling that operates on any table. The early-bound style uses generated classes (Account, Contact) produced by a model-builder tool from the live metadata, giving you compile-time property names and IntelliSense at the cost of regenerating the classes when the schema changes. Microsoft's guidance is pragmatic: use early-bound types for the tables you own and modify often, and late-bound for everything else, especially in shared libraries and migration code where schema churn would force constant regeneration.

Client-side, inside model-driven apps, the equivalent is the Xrm.WebApi JavaScript object. It exposes createRecord, updateRecord, deleteRecord, and retrieveMultipleRecords against the same underlying data, and it is the supported way to add custom logic to forms, command bars, and ribbon rules without a round-trip to a separate web server. Because it goes through the same Organization service, every call inherits Dataverse security, validation, and plug-in behavior — a client-side create will fire the same registered plug-ins as a server-side create. For Power Pages, a subset is exposed through the Portals Web API; for everything else outside the browser, use the Web API directly over HTTP or ServiceClient from .NET.

  • ServiceClient (Microsoft.PowerPlatform.Dataverse.Client) is the primary .NET client; it implements IOrganizationService and handles auth + retry
  • Late-bound (Entity + string keys) for generic, schema-agnostic tooling; early-bound (generated classes) for tables you own and change often
  • Xrm.WebApi is the client-side equivalent inside model-driven apps; it fires the same plug-ins as server-side calls
  • All paths converge on the same Organization service and inherit the same security, validation, and throttling
Dataverse data access options for developers
ContextMechanismWhen to use it
.NET server codeServiceClient (IOrganizationService)Migrations, integrations, Azure Functions, console tools — handles auth and retry-on-429
Generic / schema-agnostic codeLate-bound Entity with string keysTooling that works on any table; resilient to schema churn
Tables you own and change oftenEarly-bound generated classesCompile-time safety and IntelliSense; regenerate when schema changes
Model-driven app clientXrm.WebApi (JavaScript)Form, ribbon, and command-bar logic with no separate backend
Non-.NET or HTTP-nativeOData v4 Web API (REST)Any language; full control over batching, paging, and headers
03Application lifecycle

Solutions: the unit of deployment, and the layer model

A solution is the deployable unit in the Power Platform — a container for all the components that make up an app: tables, columns, forms, views, plug-in assemblies, cloud flows, canvas apps, web resources, connection references, and environment variables. Microsoft's ALM documentation is explicit that 'anything that can be customized in Power Apps is a solution component,' and solutions track the dependencies between those components so they deploy in the correct order. You do not deploy a single plug-in or a single form; you deploy the solution that contains them, and the platform resolves the dependency graph at import time. Treating the solution — not the individual component — as the atomic unit of change is the foundation of sane Power Platform development.

The layer model is what makes multi-team and multi-vendor customization possible without constant overwrite. Every environment stacks three layers: the system solution at the bottom (the base Dynamics 365 / Dataverse schema), one or more managed solution layers stacked above it, and a single active unmanaged layer on top where in-environment customization happens. When a component exists in multiple layers, the platform merges them according to documented rules — the topmost layer that defines a property wins, and forms and views are merged semantically rather than replaced. This is why you can install a third-party managed solution, then layer your own customizations on top without touching their code, and why removing a managed solution restores the layers beneath it.

The distinction between managed and unmanaged solutions is enforced by where they are meant to live. An unmanaged solution is the development artifact: components in it are editable, and it can be exported as either unmanaged source (for source control) or as a managed solution for deployment. A managed solution is locked once imported — its components cannot be edited directly in the target environment — which is exactly what you want in test and production. Microsoft's documented best practice is unmanaged in development, managed everywhere downstream, and it ships a dedicated guide for organizations that need to move from an existing unmanaged production setup to the managed model. Publisher and prefix are the other foundational concepts: every solution belongs to a publisher with a unique customization prefix (for example 'cont' in cont_newfield), which namespaces your custom tables and columns so they never collide with another solution's components.

  • A solution is the atomic deployable unit — it carries components and their dependency graph, imported in order
  • Three layers stack: system solution (base), managed solution layers, and one active unmanaged layer on top
  • Merges follow documented rules — topmost layer wins for properties; forms and views merge semantically
  • Unmanaged = development artifact (editable, exportable as source); managed = locked deployment artifact for downstream environments
  • Every solution has a publisher and a customization prefix that namespaces your custom schema
Managed vs unmanaged solutions — what each is for
DimensionUnmanaged solutionManaged solution
EnvironmentDevelopment onlyTest, UAT, production
Editable in targetYes — components can be changed directlyNo — locked; changes ship as solution updates
Export formatsUnmanaged (source) or managed (deployment)Managed only
Source controlExport unmanaged, unpack, and commitBuilt from committed source, then packaged managed
Removal behaviorDeleting leaves the components behind in the active layerUninstalling removes its layer and restores layers beneath
04Tooling

The Power Platform CLI: turning the maker portal into code

The Power Platform CLI — the pac command — is the bridge that makes Dataverse development feel like real software engineering rather than clicking through a web portal. It is a cross-platform command-line tool (Windows, macOS, Linux) that authenticates to environments with pac auth, manages environments with pac admin, and drives the full solution lifecycle with pac solution. The workflow that matters for developers is: initialize a solution project, develop components, unpack the solution into readable source files, pack those files back into a deployable archive, validate with the Solution Checker, and import into the target environment. Because unpack writes each component as discrete XML and JSON files under a predictable folder structure, a Dataverse solution becomes something you can diff, review in a pull request, and store in Git alongside the rest of your codebase.

The canonical pac solution sequence is worth memorizing because every CI/CD pipeline eventually encodes it. You start with pac solution init --publisher-name and --publisher-prefix to scaffold a new solution project, or pac solution clone to pull an existing solution from an environment. You develop in the dev environment (or directly in the unpacked source), then pac solution export and pac solution unpack to materialize the source, pac solution check to run the static Solution Checker against it, and pac solution pack --packagetype Both to produce both an unmanaged and a managed build. pac solution version increments the semantic version baked into the build, and pac solution import (or upgrade) deploys it. The 'Both' package type is the key to the unmanaged-in-dev, managed-in-prod rule: one unpacked source tree, two build outputs, depending on the target.

Beyond solutions, pac reaches the rest of the developer surface. pac pcf init and pac pcf push build and hot-deploy Power Apps Component Framework code components. pac plugin init scaffolds a Dataverse plug-in class library, and pac plugin push imports the compiled assembly and its registered steps — turning plug-in deployment into a repeatable command rather than a manual Plug-in Registration Tool session. pac canvas pack and unpack do the same for canvas apps, and pac connector, pac copilot, and pac pipeline extend the model to custom connectors, Copilot Studio agents, and Power Platform Pipelines (Microsoft's first-party deployment automation). The unifying promise is that nothing about a Dataverse project is irretrievably locked inside a maker portal session — every component can be unpacked, versioned, and rebuilt from source.

  • pac is cross-platform and the single entry point for auth, admin, solution, pcf, plugin, canvas, and connector operations
  • Unpack materializes a solution as discrete, diffable XML/JSON source files — enabling Git, pull requests, and code review
  • pac solution pack --packagetype Both produces unmanaged and managed builds from one source tree
  • pac plugin init / push turns plug-in scaffolding and deployment into repeatable commands
  • Power Platform Pipelines (pac pipeline) is Microsoft's first-party deployment automation built on the same primitives
The pac solution workflow for source-controlled development
CommandPurposeUsed in
pac solution initScaffold a new solution project with publisher and prefixOne-time, per solution
pac solution clone / export / unpackPull an existing solution and materialize it as source filesInitial bring-under-source-control; sync from dev
pac solution pack --packagetype BothBuild unmanaged and managed archives from sourceEvery build / release
pac solution checkRun the Solution Checker static analysisPull-request validation
pac solution versionBump the semantic version of the buildRelease tagging
pac solution import / upgradeDeploy the built archive to a target environmentCI/CD to test and prod
05Source control

Native Dataverse Git integration: source control without leaving the portal

Alongside pac solution unpack, Microsoft ships native Git integration for Dataverse: makers and developers can bind solutions in a development environment to an Azure DevOps Git repository from the Solutions experience in Power Apps, Copilot Studio, Power Automate, and Power Pages. The intent is fusion development — citizen developers commit and pull through a familiar maker UI while pro-code developers work the same repo with the CLI, Visual Studio, and CI. Git integration is meant for developer environments only; test and production still receive managed solution artifacts via pipelines or DevOps deploys, not live Git sync. Prerequisites matter: both the development environment and the target binding path require Managed Environments, and a system administrator role is needed for the initial connect. Microsoft's 2026 Wave 1 release plan also expands source-code integration toward GitHub repositories for teams that do not standardize on Azure DevOps Repos.

Operationally, unmanaged solutions in the maker environment remain the editable surface; commits push solution objects in a Git-optimized, human-readable layout designed so objects can be shared across multiple solutions in one repo without full duplication. Managed solutions are still built from source and deployed downstream so production stays locked. That preserves the same unmanaged-in-dev, managed-everywhere-else rule the CLI path uses — Git integration lowers the barrier to putting makers on source control, it does not replace managed deployments.

Code-first assets need extra discipline. Plug-ins, custom workflow activities, and PCF controls are often deployed as compiled packages. If you push a plug-in assembly straight into an unmanaged solution and then commit from the portal, the repository stores the built binary rather than (or in addition to) the C# source — two copies that drift. Microsoft's guidance is to build code-first objects in a solution build pipeline, import the generated unmanaged solution into the maker environment, and let Git sync that solution state, while the actual source lives in the same repo under normal developer folders. Fusion teams that follow that pattern keep a single source of truth; teams that commit only binaries recreate the old 'mystery DLL in production' problem with better tooling around it.

  • Native Git integration binds Dataverse solutions to Azure DevOps Git from the maker portal — fusion ALM without mandatory CLI fluency
  • Use it on developer environments only; promote managed builds to test and production
  • Requires Managed Environments and admin rights for connect/disconnect
  • Do not treat Git commits of compiled plug-in/PCF binaries as the source of truth — build from source in CI
  • pac unpack and native Git are complementary: pro-code depth vs maker-accessible source control
pac CLI unpack vs native Dataverse Git integration
Dimensionpac solution unpack / packNative Git integration
Primary usersPro-code developers and CI systemsMakers + fusion teams in the portal
Where you workTerminal, VS/VS Code, pipelinesSolutions experience (make.powerapps.com)
Repo bindingYou export/unpack and commit manually or via CIEnvironment/solution bound to Azure DevOps Git (GitHub expansion on roadmap)
Environment focusAny scripted environment with authDeveloper environments (not test/prod sync targets)
Code-first objectsSource stays in repo; pack imports built componentsPrefer CI-built unmanaged import so Git does not become binaries-only
Managed deploysYour pipeline packs managed and importsStill use Pipelines or DevOps — Git is not the production channel
06Automation

CI/CD: source control, branching, and the promotion pipeline

Application lifecycle management on the Power Platform converges on one outcome: every change to a production environment arrives as a versioned, managed solution built from source, not a manual edit. Microsoft's ALM guidance describes the healthy pattern as a source-controlled unmanaged export from a development environment, built into a managed solution by a pipeline, and imported into test and then production as a tracked release. The branching model mirrors classical software engineering: a single development environment per active branch (so makers and developers never collide), feature work unpacked and committed as source, pull requests gated by the Solution Checker, and automated builds that pack the managed artifact and tag it with a semantic version. Teams that skip the single-environment-per-branch discipline inevitably discover the unmanaged solution's worst failure mode — two people editing the same form in one shared dev environment with no merge.

The mechanics are well-trodden. In Azure DevOps, the microsoft/powerplatform-build-tools tasks wrap the pac commands into pipeline steps: export the unmanaged solution from dev, unpack, commit; on build, pack managed and publish the artifact; on release, import into test, run automated tests, then import into prod on approval. GitHub Actions has an equivalent set of actions in microsoft/powerplatform-actions — the same surface as the Azure DevOps tasks for solution sync, pack, checker, and deploy. The critical configuration decisions are environmental: connection references and environment variables must be set per-target at import time (so the same managed solution points at the right endpoints in each environment), and the solution must be built managed so the target cannot be hand-edited afterward. Microsoft's documented environment strategy is a minimum of dev, test, and production Dataverse environments, each in the same geo, with the same solution layered identically — never a shortcut where dev and prod share one environment.

Two Microsoft-native options reduce the DIY pipeline burden for smaller teams. Power Platform Pipelines — administered from the Power Platform admin center and driven by pac pipeline — provides a hosted, low-code deployment engine that moves solutions between environments along a configured stage path, with pre- and post-deployment hooks for custom validation. Pipelines always deploy managed solutions to non-development environments and default import behavior is Upgrade without Overwrite customizations. As of February 2026, Microsoft enables Managed Environments for any pipeline target environment that is not already managed — plan governance and licensing for TEST/UAT/PROD targets now rather than discovering it at deploy time. For teams already standardized on GitHub or Azure DevOps, the build-tools tasks and GitHub Actions give full control and integrate naturally with existing release management; pipelines can also be extended to call those tools. The choice is operational, not technical: both produce the same managed-import outcome. The anti-pattern to avoid in either model is treating the pipeline as optional — an environment that receives manual unmanaged edits is an environment that has drifted from source, and drifted environments are where production incidents are born.

  • Goal: every production change arrives as a versioned managed solution built from source
  • Source-controlled unmanaged export from dev; managed build promoted through test to prod
  • One development environment per active branch to avoid concurrent unmanaged edits
  • Use microsoft/powerplatform-build-tools (Azure DevOps) or microsoft/powerplatform-actions (GitHub)
  • Power Platform Pipelines is the hosted path — pipeline targets require Managed Environments (Microsoft enforcement from February 2026)
  • Match path to maturity: manual → pipelines → ADO/GitHub → ALM Accelerator as risk and team size grow
Choosing a Power Platform ALM path (2026)
PathBest forWhat you getWhat you trade
Manual export/importPrototypes, single-developer low-risk toolsSpeed and zero pipeline setupNo traceability, no approvals, no safe rollback
Power Platform PipelinesCitizen + fusion teams needing structured promotionHosted managed deploys, version history, maker UX, pac pipelineSimpler than full DevOps; targets must be Managed Environments (enforced from Feb 2026)
Azure DevOps Build ToolsMulti-developer teams, regulated or high-risk workloadsGit branching, PR gates, Solution Checker, full release managementRequires DevOps process maturity and service-principal setup
GitHub Actions (powerplatform-actions)Teams already on GitHubSame task surface as ADO: pack, check, deploy from workflowsSame discipline as ADO — not a shortcut around source control
ALM AcceleratorEnterprise discipline without building DevOps from scratchPre-configured branching, validation, and promotion on Azure DevOpsHeavier framework; evaluate fit vs native Pipelines + light CI
07Server logic

Plug-ins and the event execution pipeline

A plug-in is custom server-side business logic compiled into a .NET assembly and registered against specific events in Dataverse's event execution pipeline. Microsoft's developer documentation still defines it as a compiled class within an assembly that targets .NET Framework — plug-ins do not run on modern .NET (Core / 8) inside the sandbox — and each class registered on an event pipeline step must implement the IPlugin interface (a single IPlugin.Execute method). When a Create, Update, Delete, or other SDK message is processed, the platform walks the pipeline and invokes every plug-in registered for that message, table, and stage. Because the plug-in runs inside the platform's own process and transaction, it is the place to put logic that must execute regardless of how a row was changed — by an app, a flow, the Web API, or another plug-in — and that must participate in the same database transaction so it can validate or roll back atomically.

The recommended authoring pattern is PluginBase, generated by pac plugin init or Power Platform Tools for Visual Studio. PluginBase implements IPlugin for you, initializes a LocalPluginContext with the execution context, organization service factory, and tracing service, and you override ExecuteDataversePlugin. Whether you implement IPlugin directly or derive from PluginBase, the class must be written stateless: the platform caches instances and reuses them, so never store service instances or context data in instance fields. The pipeline is organized into stages that dictate what a plug-in can see and do. Pre-validation (stage 10) fires before the transaction is even opened, before security checks — useful for early rejection of a request. Pre-operation (stage 20) fires inside the transaction, before the core write, so the plug-in can read and modify the target Entity before it is saved. Post-operation (stage 40) fires after the core write but still within the transaction, so the plug-in can react to the saved state and trigger related work. (Stage 30 is the platform's own core operation and is not available for custom registration.) At pre-operation and post-operation a plug-in can request a pre-image or post-image — a snapshot of the row's columns before or after the operation — which it accesses through the context by an alias, avoiding a second query. Secure and unsecure configuration strings can be passed into the plug-in constructor at registration time for per-step behavior without recompiling. The Plug-in Registration Tool (or Power Platform Explorer in Visual Studio, or pac tool prt / pac plugin push) wires each plug-in class to a specific message, table, stage, filtering attributes, and execution mode.

Two hard constraints shape every plug-in design, and ignoring them is the most common cause of production timeouts. First, there is a firm two-minute limit for a Dataverse message operation to complete, and Microsoft documents that 'this limit includes executing the intended message operation and all registered synchronous and asynchronous plug-ins' — so multiple synchronous plug-ins on one message share a single two-minute budget. There are also limits on the CPU and memory a plug-in may consume. Second, plug-in execution has a depth limit: the chain of plug-in-triggered operations is capped at eight, after which the platform throws an 'infinite loop' fault. The engineering implications are consistent — keep synchronous plug-ins short and transactional, offload anything slow or external to an asynchronous step or to Power Automate, never query or update the same row you are processing inside a step that fires on its own update (that is how depth loops are born), and use filtering attributes so a plug-in only runs when the columns it cares about actually changed.

  • Plug-ins target .NET Framework in the sandbox — not modern .NET; implement IPlugin.Execute or derive from PluginBase
  • PluginBase (from pac plugin init) supplies LocalPluginContext; keep implementations stateless because instances are cached
  • Stages: pre-validation (10), pre-operation (20), post-operation (40); stage 30 is the platform core and not registrable
  • Pre- and post-images give a plug-in the row's columns before/after the operation without a second query
  • Hard two-minute limit covers the message plus all synchronous plug-ins; CPU and memory are also capped
  • Depth limit of 8 prevents infinite plug-in loops — never update the triggering row inside its own step without a guard
The event execution pipeline stages available to plug-ins
StageWhen it firesTypical use
Pre-validation (10)Before security checks, before the transaction opensEarly rejection; validation that needs no DB write
Pre-operation (20)Inside the transaction, before the core writeRead/modify the target Entity before it is saved; use a pre-image
Post-operation (40)After the core write, still in the transactionReact to saved state; trigger related work; use a post-image
AsynchronousAfter the transaction commits, on a background workerSlow or external work that must not block the user
08Decision

Plug-ins, Custom APIs, or Power Automate: where the logic goes

Dataverse offers several ways to run custom logic, and choosing the right one is a recurring architectural decision. Power Automate cloud flows are the lowest-friction option: they trigger on row events, compose visually, and integrate with hundreds of systems, but they run outside the Dataverse transaction, have higher latency (seconds rather than milliseconds), and consume per-flow and connector licensing. Business rules and Power Fx are configuration, not code — they enforce simple validation and field defaults in the model-driven UI but cannot run server-side. Plug-ins, as covered above, run inside the transaction with deterministic, low-latency execution and full access to the Organization service, at the cost of .NET development and the two-minute budget. Custom APIs sit between the two: they let you define a bespoke message (with typed input and output parameters) backed by a plug-in, callable from Power Automate, Power Apps, or external code as a single, well-documented operation.

The decision routes cleanly on two axes: does the logic need to run inside the transaction, and is it triggered automatically or called explicitly? Transactional, automatic, must-run-on-every-change logic belongs in a synchronous plug-in (pre-operation for validation, post-operation for related updates). Explicit, reusable business operations — 'approve invoice,' 'calculate commission,' 'sync to ERP' — belong in a Custom API, which gives you a clean contract and lets callers (a flow, an app, or external code) invoke it without knowing its internals. Cross-system, event-driven, human-or-system orchestrated work belongs in Power Automate, ideally calling a Custom API for the heavy Dataverse logic rather than reimplementing it in flow actions. The anti-pattern is putting transactional validation in Power Automate — a flow cannot roll back the Dataverse write that triggered it, so validation that must guarantee data integrity has to live in a synchronous plug-in.

A healthy Dataverse estate usually has all three coexisting. Business rules and form scripts handle the UI layer; synchronous plug-ins enforce the non-negotiable transactional invariants; Custom APIs expose reusable operations to flows and external callers; Power Automate orchestrates the human and cross-system workflows that wrap them. The discipline is to put each responsibility where its guarantees match the requirement — transactional integrity in plug-ins, reusability and a clean contract in Custom APIs, integration and orchestration in Power Automate — and to resist the temptation to put everything in one bucket because it is the one the team knows best.

  • Routes on two axes: transactional vs not, automatic vs explicitly called
  • Synchronous plug-ins for transactional invariants; Custom APIs for reusable callable operations
  • Power Automate for cross-system orchestration — but call a Custom API for heavy logic, do not reimplement it in flow
  • Never put must-rollback validation in Power Automate — a flow cannot undo the triggering write
Where server logic belongs in Dataverse
Logic typeBest mechanismWhy
Transactional validation, must run on every changeSynchronous plug-in (pre-op)Runs inside the transaction; can reject and roll back atomically
Reusable business operation with typed parametersCustom API backed by a plug-inClean callable contract; invoked by flows, apps, and external code
Cross-system or human orchestrationPower Automate cloud flowHundreds of connectors; visual composition; outside the transaction
Simple UI field validation / defaultsBusiness rules / Power FxConfiguration, no code, enforced in the model-driven UI
Slow or external work triggered by a changeAsynchronous plug-in or flowDoes not consume the synchronous two-minute budget
09Configuration

Connection references and environment variables: portable configuration

A managed solution that works in dev must point at the right endpoints and credentials when it lands in test and production — and hard-coding those inside a flow or a plug-in is the most common reason a promotion breaks. Dataverse solves this with two solution-aware configuration components. A connection reference abstracts a named connection to an external system (an SMTP server, a SharePoint site, a custom connector) so that the same flow uses connection 'SharePoint – Dev' in one environment and 'SharePoint – Prod' in another; the flow references the connection by name, and each environment resolves the name to the right credential at runtime. An environment variable abstracts a single configuration value — a URL, a key, a threshold — with a current value that is set per environment, so a flow or plug-in reads 'EnvironmentUrl' and gets the correct value wherever the solution is imported.

Both components ship inside the solution and are first-class citizens of the ALM story. When you pac solution unpack a solution that contains them, the connection reference and environment variable definitions are materialized as source, while their per-environment values are set at import time — either interactively in the maker portal or programmatically through the pipeline. This is what makes a solution genuinely portable: the structure (what connections and variables exist) is committed, while the binding (which credential and which value apply) is environmental. Microsoft's documentation treats them as the standard mechanism for moving solutions between environments without editing the flows inside them, and every healthy pipeline sets environment variable values and resolves connection references as an explicit step of the import.

The operational rule is to identify your configuration boundaries early. Any value that differs between dev, test, and production — an external API base URL, a notification recipient, a feature flag, a connector credential — should be an environment variable or a connection reference from the first build, not retrofitted after a promotion has already failed. Retrofitting is painful because it means unpacking, editing, and re-packing a solution that is already deployed; designing them in from day one costs minutes and saves days. Treat connection references and environment variables as the parameterization layer of your Dataverse application, exactly the way you treat environment-specific config in any other software system.

  • Connection references abstract named external connections so the same flow resolves the right credential per environment
  • Environment variables abstract per-environment values (URLs, keys, thresholds) read by flows and plug-ins at runtime
  • Both are solution-aware: definitions ship in the solution, values are set per environment at import time
  • Identify configuration boundaries on day one — retrofitting onto a deployed solution is expensive
10Toolchain

The developer toolchain: Visual Studio, the PRT, and the Solution Checker

Dataverse development rests on a small, stable toolchain that every developer on a project should know. Visual Studio (with the Power Platform Tools extension) provides the Power Platform Explorer view — a tree of your connected environment's solutions, assemblies, and steps — and project templates for plug-in libraries and PCF components. From Power Platform Explorer you register and update plug-in steps, add pre- and post-images, deploy assemblies, and install the Plug-in Registration Tool without leaving the IDE. The standalone Plug-in Registration Tool (PRT) remains the classic interface for the same job and is still the most reliable way to register steps, configure filtering attributes, and inspect an existing registration set. Both write the same metadata; the choice is preference and whether you are in Visual Studio or a standalone session.

For debugging, the Plug-in Profiler captures the exact context handed to a plug-in during a real execution and lets you replay it locally against your source code, so you can step through a failed production plug-in in Visual Studio without reproducing the exact data and timing that triggered it. The plug-in trace log — written from your code via ITracingService — is the runtime observability layer: turn it on in the environment, write structured trace lines from every plug-in, and read the resulting logs to diagnose failures that only manifest under production load. The Solution Checker (pac solution check) is the static analysis gate: it inspects a packed solution for performance, maintainability, and best-practice issues across plug-in code, form scripts, and flow definitions, and it is the single most effective guardrail when wired into pull-request validation.

Rounding out the toolchain: pac itself for everything scripted and repeatable, the model-driven app Power Apps Studio and the form designer for the configuration layer, and the Power Apps Test Engine (pac test) for automated UI and logic testing of model-driven and canvas apps. The discipline that pays off is to keep as much as possible in the scripted layer — pac and the build tools — and reserve the interactive tools (PRT, Power Apps Studio) for the work that genuinely needs a UI. A team whose entire solution can be rebuilt, checked, and deployed from the command line is a team whose changes can be reviewed, audited, and rolled back; a team that depends on remembering which clicks were made in a portal is a team one absent colleague away from a stuck release.

  • Visual Studio + Power Platform Tools and the standalone PRT both register plug-in steps and images
  • Plug-in Profiler replays a captured production context locally for step-through debugging
  • ITracingService writes structured traces; the trace log is the runtime observability layer
  • Solution Checker is the static-analysis gate — wire it into pull-request validation
  • Prefer scripted tooling (pac) over interactive tools for anything that needs to be repeatable
The Dataverse developer toolchain and what each tool does
ToolLayerPurpose
Power Platform CLI (pac)ScriptedSolution, plug-in, PCF, canvas, connector lifecycle — the CI/CD foundation
Visual Studio + Power Platform ToolsInteractivePlug-in authoring, Power Platform Explorer, step and image registration
Plug-in Registration Tool (PRT)InteractiveRegister steps, filtering attributes, and images; inspect existing registrations
Plug-in ProfilerDebuggingCapture and replay a real plug-in context locally for step-through debugging
ITracingService / trace logObservabilityStructured runtime traces from plug-ins for production diagnosis
Solution Checker (pac solution check)QualityStatic analysis for performance and best practice; PR gate
11Field notes

Anti-patterns that bite, and a build checklist

Certain mistakes recur across Dataverse projects because they look reasonable and fail predictably. Editing a shared unmanaged development solution with multiple people simultaneously produces silent overwrites with no merge — the fix is one dev environment per active branch, with changes unpacked and committed as source. Shipping an unmanaged solution to production means the production components are editable in place, so there is no single source of truth for what is deployed — the fix is managed builds for every downstream environment. Putting transactional validation in Power Automate breaks atomicity because a flow cannot roll back the write that triggered it — the fix is a synchronous pre-operation plug-in. Writing a plug-in that updates the same row it is registered on, on Update, creates an infinite loop that the platform kills at depth eight — the fix is filtering attributes and a guard that checks whether the value actually changed before writing.

Other anti-patterns are performance rather than correctness. A synchronous plug-in that calls an external HTTP endpoint, or loops over related rows with per-row queries, will blow the two-minute budget under load — move that work to an asynchronous step or a Custom API called from a flow. Ignoring service-protection limits in integration code (defaults of 6,000 requests and 20 minutes of combined execution time per user per web server in a five-minute sliding window, plus about 52 concurrent requests, returning HTTP 429 with Retry-After) produces throttling storms in production — paginate, batch carefully, and implement retry-on-429, or use ServiceClient which handles retry for you. Hard-coding environment-specific endpoints or credentials inside a flow or plug-in breaks promotions — use connection references and environment variables. Committing only compiled plug-in DLLs or PCF bundles to Git while keeping source elsewhere creates dual sources of truth — build code-first assets in CI and import the unmanaged solution so source remains authoritative. And skipping the Solution Checker in the pull-request gate lets performance and maintainability issues ship that are expensive to find later.

A pre-build checklist collapses this into a repeatable routine. Is the change packaged in a solution with a publisher and prefix, and committed as unpacked source? Does the solution build as both unmanaged and managed from one source tree? Are environment-specific values in environment variables and connections in connection references, not hard-coded? Is the heaviest, transactional logic in synchronous plug-ins, with slow and external work in asynchronous steps or flows? Does every plug-in have filtering attributes, a depth-loop guard, and ITracingService traces? Does the pull request run pac solution check and block on its findings? And does the pipeline import a managed, version-tagged build into test and then production, with the same solution layered identically in each? A change that answers all of those cleanly is a change that ships safely and can be rolled back; a change that cannot answer them is not ready, regardless of how well it works in the developer's environment.

  • Anti-pattern: shared unmanaged dev edits (no merge) — use one dev environment per branch
  • Anti-pattern: unmanaged in production — ship managed builds downstream
  • Anti-pattern: transactional validation in Power Automate — use a synchronous pre-operation plug-in
  • Anti-pattern: a plug-in that re-updates its own row on Update — filter attributes and guard on real change
  • Anti-pattern: binaries-only for plug-ins/PCF in Git — build from source in CI and keep source as truth
  • Checklist: solution + publisher + prefix, committed source, Both build, env vars + connection references, traced and filtered plug-ins, Solution Checker in the PR gate, managed versioned imports across environments
FAQ

Frequently asked questions

What is the primary way developers access Dataverse from .NET code?

Use the ServiceClient class in the Microsoft.PowerPlatform.Dataverse.Client namespace. It implements IOrganizationService — the interface at the core of the SDK — and adds authentication against Microsoft Entra ID (OAuth, client secret, or client certificate), connection management, and automatic retry on HTTP 429 throttling. For server-side work such as migrations, integrations, console tools, and Azure Functions, instantiate ServiceClient once and reuse it for the lifetime of the process. Client-side inside model-driven apps, the equivalent is the Xrm.WebApi JavaScript object; for non-.NET languages, use the OData v4 Web API over HTTP.

What is the difference between late-bound and early-bound code in Dataverse?

Late-bound code uses the generic Entity class and string column keys — for example new Entity("account") and entity.GetAttributeValue<string>("name"). It is loosely typed, resilient to schema changes, and the natural choice for generic tooling that works on any table. Early-bound code uses classes (Account, Contact) generated by a model-builder tool from the live metadata, giving compile-time property names and IntelliSense, at the cost of regenerating the classes whenever the schema changes. The pragmatic rule is early-bound types for tables you own and modify often, and late-bound for shared libraries and migration code where schema churn would force constant regeneration.

What is the difference between a managed and an unmanaged solution?

An unmanaged solution is the development artifact: its components are editable in the environment, and it can be exported as either unmanaged source (for source control) or as a managed solution for deployment. A managed solution is locked once imported — its components cannot be edited directly in the target environment — which is exactly what you want in test and production. Microsoft's documented best practice is unmanaged in development and managed everywhere downstream. Never ship an unmanaged solution to production, because editable production components have no single source of truth and cannot be cleanly versioned or rolled back.

How do I put a Dataverse solution under source control?

Use the Power Platform CLI (pac). Export the unmanaged solution from your development environment with pac solution export, then pac solution unpack to materialize it as discrete, diffable XML and JSON source files under a predictable folder structure. Commit those files to Git. On build, run pac solution pack --packagetype Both to produce both an unmanaged and a managed archive from the same source tree, then import the managed build into test and production through your pipeline. Run pac solution check in your pull-request validation to catch performance and best-practice issues before they merge.

What is a Dataverse plug-in and how is it registered?

A plug-in is a .NET class that implements the IPlugin interface (a single Execute method), compiled into an assembly and registered against specific events in the Dataverse event execution pipeline. You register it on a combination of SDK message (Create, Update, Delete, and others), target table, and execution stage — pre-validation (10), pre-operation (20), or post-operation (40). Registration is done with the Plug-in Registration Tool or Power Platform Explorer in Visual Studio, where you also set filtering attributes (so the plug-in only runs when specific columns change) and configure pre- and post-images (snapshots of the row before or after the operation). A plug-in runs inside the platform's transaction, so it can validate, modify, or roll back atomically.

What are the execution limits for Dataverse plug-ins?

There is a hard two-minute limit for a Dataverse message operation to complete, and it includes the core operation plus all registered synchronous and asynchronous plug-ins — so multiple synchronous plug-ins on one message share a single two-minute budget. The platform also caps the CPU and memory a plug-in may consume. Plug-in execution has a depth limit of eight: a chain of plug-in-triggered operations longer than eight throws an infinite-loop fault. The practical guidance is to keep synchronous plug-ins short and transactional, move slow or external work to an asynchronous step or Power Automate, use filtering attributes so a plug-in only runs when the columns it cares about changed, and never update the same row that triggered the plug-in without a guard against unchanged values.

How do connection references and environment variables help with deployments?

Both are solution-aware configuration components that make a solution portable across environments. A connection reference abstracts a named connection to an external system, so the same flow resolves 'SharePoint – Dev' in one environment and 'SharePoint – Prod' in another without editing the flow. An environment variable abstracts a single per-environment value — a URL, key, or threshold — that flows and plug-ins read at runtime. The definitions ship inside the solution as source, while the per-environment values and credential bindings are set at import time, either in the maker portal or programmatically through your pipeline. Identify configuration boundaries on day one, because retrofitting connection references and environment variables onto a deployed solution is expensive.

When should I use a plug-in versus Power Automate versus a Custom API?

Use a synchronous plug-in for transactional logic that must run on every change to a row and must be able to roll back atomically — for example, validation that guarantees data integrity. Use Power Automate for cross-system or human orchestration, where you need hundreds of connectors and visual composition and you do not need to roll back the triggering write. Use a Custom API when you want to expose a reusable, explicitly callable business operation with typed input and output parameters — a flow, an app, or external code can invoke it as a single, well-documented operation. A healthy estate usually combines all three: plug-ins for invariants, Custom APIs for reusable operations, and Power Automate for the orchestration that wraps them.

What is native Dataverse Git integration and when should I use it?

Native Git integration lets you bind solutions in a development Dataverse environment to an Azure DevOps Git repository from the Solutions experience in Power Apps and related makers portals. Makers can commit and pull without living in the CLI, while pro-code developers use the same repository with pac, Visual Studio, and CI. Use it to put fusion teams on source control quickly; keep test and production on managed solution deployments via Power Platform Pipelines or Azure DevOps/GitHub Actions. It requires Managed Environments and is not a substitute for packing managed builds for production.

Do Power Platform Pipelines require Managed Environments?

Yes for pipeline targets. Microsoft documents that environments used as pipeline targets must be enabled as managed environments, and starting February 2026 Microsoft begins enabling Managed Environments for any pipeline target that is not already managed. Review TEST, UAT, and production targets now — either enable Managed Environments manually or configure automatic conversion in the Power Platform admin center under Deployments settings. Managed Environments also imply premium licensing considerations for those environments.

Should Dataverse plug-ins target .NET Framework or modern .NET?

In-process Dataverse plug-ins still target .NET Framework and run in the platform sandbox. Microsoft's write-a-plug-in documentation describes a plug-in as a compiled class within an assembly that targets .NET Framework and implements IPlugin. Use pac plugin init or Power Platform Tools to generate the recommended PluginBase pattern, keep the class stateless (the platform caches instances), and put long-running or modern-.NET work outside the sandbox — for example Azure Functions called asynchronously — rather than expecting .NET 8 plug-in hosting inside Dataverse.

How do I choose between Power Platform Pipelines and Azure DevOps or GitHub?

Choose Power Platform Pipelines when you need structured managed promotion with a maker-friendly UI and light setup. Choose Azure DevOps Build Tools or GitHub Actions (microsoft/powerplatform-actions) when multiple developers need Git branching, pull-request gates, Solution Checker in CI, and formal release approvals. The ALM Accelerator is a pre-configured enterprise layer on Azure DevOps if you want discipline without designing the whole framework. Many teams use Pipelines for core deploys and extend them with DevOps for advanced gates — both paths should still ship versioned managed solutions built from source.

Sources & methodology

18 cited

Every pricing figure and statistic on this page is traced to a primary or vendor source with a verification date. Where partner pages are cited, their platform bias is disclosed in-line.

  1. 01
    Dataverse is metadata-driven — all data about the data is itself stored and queryable at runtime, and the schema (tables, columns, relationships) is edited as metadata rather than SQL scriptslearn.microsoft.com · verified Microsoft Learn — 'What is Microsoft Dataverse?' documents the metadata-driven model, standard and custom tables, and the reuse of rules and security across consuming apps.
  2. 02
    ServiceClient (Microsoft.PowerPlatform.Dataverse.Client) implements IOrganizationService and is the primary .NET client for Dataverse, supporting OAuth, client secret, and client certificate authentication with retry-on-429learn.microsoft.com · verified Microsoft Learn — 'Use the Organization service' describes the Organization service, the ServiceClient class, IOrganizationService, and the supported authentication methods.
  3. 03
    A plug-in is a compiled .NET class implementing IPlugin.Execute, registered against messages, tables, and stages in the event execution pipeline; the pipeline exposes pre-validation, pre-operation, and post-operation stageslearn.microsoft.com · verified Microsoft Learn — 'Write a plug-in' states verbatim that 'a plug-in is a compiled class within an assembly that targets .NET Framework' and that each registered class 'must implement the IPlugin interface, which defines a single IPlugin.Execute method.'
  4. 04
    The event execution pipeline exposes stages developers register against; the Plug-in Registration Tool configures plug-ins, Azure integrations, virtual table data providers, and webhooks on those eventslearn.microsoft.com · verified Microsoft Learn — 'Event Framework in Microsoft Dataverse' documents the pipeline stages and the role of the Plug-in Registration Tool.
  5. 05
  6. 06
    A solution is a container for solution components (anything that can be customized), and solutions track dependencies between components to ensure correct deployment order; components can be nestedlearn.microsoft.com · verified Microsoft Learn — 'Solution concepts' (ALM) documents solution components, dependency tracking, nesting, and the publisher and prefix model.
  7. 07
    Dataverse stacks a system solution at the base, managed solution layers above it, and a single active unmanaged layer on top; component property resolution and semantic merges follow documented layering ruleslearn.microsoft.com · verified Microsoft Learn — 'Solution layers' (ALM) documents the layer stack, the unmanaged layer on top, and how the platform merges components across layers.
  8. 08
    Microsoft's ALM best practice is unmanaged solutions in development and managed solutions for deployment to downstream environments; a dedicated guide covers moving an existing unmanaged setup to managedlearn.microsoft.com · verified Microsoft Learn — 'Move from unmanaged to managed solutions' documents the transition strategy and the unmanaged-in-dev, managed-in-prod best practice.
  9. 09
    The Power Platform CLI (pac) is a cross-platform command-line tool providing pac auth, pac admin, pac solution (init/clone/unpack/pack/check/version/import), pac pcf, pac plugin, pac canvas, pac connector, and pac pipeline operationslearn.microsoft.com · verified Microsoft Learn — 'What is Microsoft Power Platform CLI?' documents the cross-platform availability and the command groups including solution, pcf, plugin, canvas, connector, and pipeline.
  10. 10
    Dataverse service-protection API limits are 6,000 requests, 20 minutes of execution time, and roughly 52 concurrent requests per user per web server in a five-minute sliding window, returning HTTP 429 with Retry-Afterlearn.microsoft.com · verified Microsoft Learn — 'Service protection API limits' documents the three facets, per-web-server defaults, and the 429 + Retry-After behavior inherited by the SDK, the Web API, and connectors.
  11. 11
    Connection references and environment variables are solution-aware configuration components that abstract per-environment connections and values so a solution promotes between environments without editing the flows inside itlearn.microsoft.com · verified Microsoft Learn — 'Application lifecycle management (ALM) basics' documents connection references and environment variables as solution components used to move solutions between environments, alongside the dev/test/production environment strategy.
  12. 12
    Custom APIs define custom messages with typed input/output parameters backed by plug-ins, callable from Power Automate, Power Apps, and external code, and can participate in Dataverse transactionslearn.microsoft.com · verified Microsoft Learn — 'Create and use Custom APIs' documents custom messages, typed parameters, plug-in backing, transactional participation, and invocation from flows, apps, and external code.
  13. 13
    Native Dataverse Git integration syncs solutions with Azure DevOps Git from the maker portal for fusion teams; intended for developer environments with managed solution deploys to test/prodlearn.microsoft.com · verified Microsoft Learn — 'Overview of Git integration in Power Platform' documents benefits, unmanaged-vs-managed with Git, and code-first fusion guidance.
  14. 14
    Dataverse Git integration setup requires Managed Environments; system administrator role binds the environment/solution to Azure DevOpslearn.microsoft.com · verified Microsoft Learn — 'Dataverse Git integration setup' lists Managed Environment prerequisites and connect flow.
  15. 15
    Power Platform Pipelines target environments must be Managed Environments; starting February 2026 Microsoft enables managed environments for pipeline targets that are not already enabledlearn.microsoft.com · verified Microsoft Learn — 'Overview of pipelines in Power Platform' Important note on February 2026 managed environment enablement for pipeline targets.
  16. 16
    Power Platform GitHub Actions (microsoft/powerplatform-actions) automate solution sync, build artifacts, deployment, and Solution Checker — same task surface as Azure DevOps Build Toolsgithub.com · verified GitHub microsoft/powerplatform-actions README describes Actions for Power Platform ALM including checker and deploy.
  17. 17
    Practical ALM path choice spans manual export/import, native Power Platform Pipelines, Azure DevOps CI/CD, and the ALM Accelerator depending on maturity and riskerpsoftwareblog.com · verified ERP Software Blog (Feb 2026) — 'Choosing Your Power Platform ALM Path' summarizes the four common maturity-aligned paths.
  18. 18
    Microsoft Power Platform Build Tools for Azure DevOps automate export/pack/import and Power Apps checker (Solution Checker) static analysislearn.microsoft.com · verified Microsoft Learn — 'Microsoft Power Platform Build Tools for Azure DevOps' documents tasks including checker.

Related services & solutions

Need a partner who builds Dataverse like real software?

Book an ERP Readiness Call with Flectic. We are a platform-neutral partner implementing Dynamics 365 and the Power Platform for SMEs across Canada, the UK, and the US, with AI-accelerated delivery designed to ship up to 3x faster. In 30 minutes we will review your solution architecture, your ALM maturity (unmanaged-in-dev, managed-in-prod, source-controlled), your plug-in and Custom API footprint, and your CI/CD pipeline, and tell you concretely what to fix before it bites in production.

Book an ERP Readiness Call
Response within one business day