Dataverse plug-ins explained
By Emil Björk · Microsoft business apps consultant, Gothenburg
How Dataverse plug-ins work — pipeline stages, sync vs async, registration, debugging, and when to use a plug-in vs a Power Automate flow.
On this page (12)
A Dataverse plug-in is a piece of .NET code that runs server-side in response to Dataverse events. Plug-ins are the most powerful customisation mechanism in the CRM-side Dynamics 365 stack — they run in the same transaction as the originating operation, can read and modify the entire context, and can call any external service. They are also the most demanding mechanism, with operational realities that Power Automate flows usually let you avoid.
The event pipeline
Every Dataverse operation (Create, Update, Delete, Associate, etc.) runs through a defined pipeline with four stages where plug-ins can register:
-
Pre-validation (stage 10) — runs outside the database transaction, before any validation. Used for plug-ins that need to inspect or modify the request before security or duplicate-detection runs. Common for caller-context-based logic.
-
Pre-operation (stage 20) — runs inside the transaction, before the database write. Used to modify the input target (e.g. compute a derived field that should be stored), or to throw an exception to cancel the operation.
-
Main operation — the database write itself. No custom plug-ins here.
-
Post-operation (stage 40) — runs inside the transaction, after the database write. Used to do follow-on work that should be transactional with the main operation: write a related record, raise a notification, call an external API (carefully).
Synchronous vs asynchronous
Plug-ins register as either:
-
Synchronous — runs immediately, blocks the user's request until complete. Failures roll back the transaction. Use for operations that must complete before the user moves on, or that need transactional rollback semantics.
-
Asynchronous — queued for later execution by the async service. Failures are retried; the user doesn't wait. Use for follow-on work that doesn't need to complete synchronously: notifications, integrations, indexing.
The execution context
Each plug-in receives an IPluginExecutionContext with the originating message, the target entity, the user, the depth (to prevent infinite recursion if plug-ins trigger each other), and shared variables for inter-plug-in data passing.
Registration
Plug-ins are packaged as signed .NET assemblies and registered through the Plug-in Registration Tool or via solution import. Registration includes: assembly, plug-in class, message (Create/Update/etc.), entity, stage, filtering attributes (only fire when these fields change), and async flag.
When to use plug-ins vs Power Automate flows.
- Use plug-ins when:
- The logic must be in the same transaction as the originating operation.
- Performance demands milliseconds rather than seconds.
- You need access to the pre/post images of the entity.
- You need to throw exceptions to cancel the operation cleanly.
- Use Power Automate flows when:
- The logic is asynchronous and orchestration-heavy.
- It crosses systems through connectors.
- It involves human approval.
- Maintenance is by low-code makers, not developers.
In modern practice, flows replace many plug-ins; plug-ins survive for transactional, performance-sensitive, server-only logic.
Debugging
Plug-ins are notoriously hard to debug. The Plug-in Registration Tool supports plug-in profiling that captures runtime context and lets you replay it locally in Visual Studio. Trace logs are visible in the Plug-in Trace Log if enabled.
Operational caveats
Synchronous plug-ins block the user; slow plug-ins make the system feel slow. Asynchronous plug-ins compete for the async service queue; bursty volume can backlog. External API calls from inside plug-ins risk timeouts; wrap them or move to async.
The sandbox and its limits
Every plug-in runs in the sandbox — an isolated .NET process with strict resource limits enforced by Dataverse. Understand these or your plug-in will fail in ways the local debugger never surfaces:
- Two-minute execution ceiling. A single plug-in step must complete within roughly two minutes. Long-running work does not belong in a plug-in — offload to an Azure Function via Service Bus or to an async job.
- Outbound HTTP is allowed but limited. Sandbox plug-ins may call external services, but only to the ports Microsoft permits (80, 443, plus a small set) and with no local filesystem, no P/Invoke, no reflection into
System.Configuration, and no arbitrary threading. UseHttpClientwith a short timeout and setServicePointManagerearly only if you truly need it. - Memory and IL restrictions. Assemblies must be signed, sized reasonably, and only reference types on the sandbox allow-list. A build that works on your machine can silently fail to register — check the registration tool's error, not the runtime.
- Impersonation is scoped. A plug-in can call
IOrganizationServiceas the initiating user, the system user, or a specific user configured on the step — but the calling user's security roles still constrain what the executed operation can do. Read impersonation in plug-ins before assuming "run as SYSTEM" solves your permissions problem.
Decision matrix — plug-in vs flow vs Azure Function
The choice is not binary. Three tools, three homes:
| Requirement | Right home |
|---|---|
| Same transaction as the write, rollback semantics on failure | Plug-in (sync) |
| Sub-second response required inside the user's request | Plug-in (sync) |
| Pre/post image comparison of an entity | Plug-in |
| Cross-system connector logic, human approval, orchestration | Power Automate flow |
| Low-code maintenance by makers | Power Automate flow |
| Long-running work (> 2 min), heavy compute, complex retry | Azure Function via Service Bus |
| Scheduled batch that shouldn't touch user requests | Azure Function or async Dataverse job |
| Simple derived field on the row being written | Formula column or plug-in — try formula first |
Notice the modern pattern: use the platform's declarative tools first (formula columns, business rules, real-time workflows), reach for Power Automate for orchestration, and reserve plug-ins for the narrow set of transactional, latency-sensitive, image-comparison workloads that nothing else fits.
Common mistakes
- Calling out to slow external APIs from a synchronous plug-in. A 4-second HTTP timeout on the vendor side becomes a 4-second freeze for every user who touches the record. Always async-out, or wrap with a hard timeout of 2 seconds and a fallback.
- Registering on all attributes. Update plug-ins should specify filtering attributes — otherwise they fire on every save, even when the fields they care about did not change. This is the single largest performance-tax mistake in real environments.
- Infinite recursion because of
depth. A plug-in that updates the same record it was triggered on will loop unless you checkcontext.Depthor useSharedVariablesto signal "I already ran". The default recursion limit is 8; hitting it throws. - Trace-log noise. The Plug-in Trace Log is off by default and can grow fast when on. Enable it, harvest what you need, then turn it back down. Leaving it on
Exception(notAll) in production is a defensible balance. - Registering messages that don't exist for the entity.
Retrieveon a custom table works;Retrieveon an out-of-box table with a specific message name may not. Always test the exact message + entity pair before deploying. - Assuming a plug-in and its flow can both handle a scenario. If both are registered and both retry on failure, you can double-post. Own each event with one mechanism.
Wrong-fit signals
If you are writing plug-ins to:
- Send emails or notifications when nothing needs to be transactional — that is a flow.
- Call a REST API on save "for logging" — that is a Service Bus enqueue and a downstream consumer.
- Compute a value that could be a formula column — that is a formula column.
- Enforce a business rule a user should see immediately in the UI — that is a business rule, or on-load JavaScript in the model-driven form.
Each of those choices moves the logic to a lower-maintenance surface with no cost to what the user experiences.
Rollout checklist
- Choose the mechanism using the decision matrix above; document the reason on the code review.
- Register on specific filtering attributes; never on the whole entity for an Update step.
- Enable the Plug-in Trace Log during rollout, tune it down in steady state.
- Wrap external calls in a bounded
HttpClienttimeout; prefer async pipeline. - Cover the plug-in with unit tests using
FakeXrmEasyor the Microsoft Power Platform Tools test harness — replay real profiler captures where possible. - Deploy through solutions, not manual registration.
- Monitor the async system jobs view for backlogs, especially after a data migration.
Where to go next
Next: the plug-in execution pipeline for exactly what runs when, plug-in exceptions explained for the errors you will meet, and plug-ins vs Power Automate for the decision that precedes writing one. The Plug-in Registration Tool covers registration and profiling; impersonation in plug-ins covers running as someone else.
Frequently asked questions
What are the stages of the Dataverse plug-in pipeline?
- Pre-validation (stage 10) runs outside the database transaction before security and duplicate detection; pre-operation (stage 20) runs inside the transaction before the write and can modify the target or cancel the operation; post-operation (stage 40) runs inside the transaction after the write for transactional follow-on work.
When should I use a plug-in instead of a Power Automate flow?
- When the logic must run in the same transaction as the triggering operation, needs to complete in milliseconds, needs pre- and post-images, or must throw an exception to cancel the operation cleanly. Flows win for asynchronous orchestration, cross-system connectors, human approvals, and anything a low-code maker will maintain.
What is the difference between synchronous and asynchronous plug-ins?
- Synchronous plug-ins block the user's request until they finish, and a failure rolls back the transaction. Asynchronous plug-ins queue for the async service, retry on failure, and do not make the user wait — the right choice for notifications, integrations, and indexing.
How do I debug a Dataverse plug-in?
- Use the Plug-in Registration Tool's profiler to capture the runtime context and replay it locally in Visual Studio, and enable the Plug-in Trace Log to see trace output. Keep external API calls out of synchronous plug-ins or wrap them tightly — a timeout there freezes the user.
Related guides
- Low-code plug-ins in DataverseHow Dataverse low-code plug-ins let makers run server-side logic without C# — Power Fx, when to use them vs Power Automate vs traditional plug-ins.
- What is Microsoft Dataverse?Microsoft Dataverse is the relational data platform underneath Dynamics 365 CRM apps and Power Platform — what it is, why it's more than a database.
- The Dataverse security modelRoles, business units, teams, row sharing, and field-level security in Dataverse — the layers that protect data in Dynamics 365 CRM and Power Apps.
- Dataverse data model fundamentalsThe Dataverse data model — tables, columns, relationships, choices, security roles, and how it sits under Dynamics 365 and the Power Platform.
- Bulk delete jobs in DataverseHow Dataverse's bulk delete handles mass record cleanup — scheduling, filters, retention policies, and the operational discipline around storage management.
Browse every guide in Customer Engagement or just Dataverse platform.
Was this helpful?
Signals which guides land and which need work. No account, no comment box — corrections go through the contact page.
Spot something wrong or want a topic covered? Send a correction or a topic request — both are welcome.