The Dataverse plug-in execution pipeline
How Dataverse executes plug-ins — stages, pre/post images, transactional scope, and how multiple plug-ins interact on a single operation.
When an external call modifies a Dataverse record, multiple plug-ins, workflows, business rules, and platform logic may fire. Understanding the execution pipeline — the order and scope in which these run — is essential for any developer writing plug-ins or debugging behaviour that seems unpredictable.
The five stages.
- Stage 10 — Pre-validation — outside the database transaction. Used for early gates, custom auth, plugin-level redirects.
- Stage 20 — Pre-operation — inside transaction, before platform DB write. Modify the incoming target before commit.
- Stage 30 — Platform operation — the database write itself.
- Stage 40 — Post-operation (in transaction) — inside transaction, after DB write but before commit. Effects can be rolled back on exception.
- Stage 50 — Post-operation (out of transaction) — after commit. Async by default; out of the user's transaction scope.
Each plug-in registers at a specific stage; only Stage 20 and 40 plug-ins can directly affect the operation's outcome.
Stage 10 specifics.
- Outside transaction.
- Can cancel the operation by throwing.
- Limited by what's not yet committed (record doesn't exist yet for Create).
- Useful for early validations that don't need transactional rollback.
Stage 20 — pre-operation, in-transaction.
- Inside transaction.
- Can modify the
Targetentity — changes flow to the DB write. - Can throw exception, rolling back.
- Most validation logic goes here.
Example use: enforce business rule that account category must match account type.
Stage 30 — platform operation.
- The DB write itself.
- Plug-ins do not register here.
- Triggers cascade behaviour (related records updates, indexes).
Stage 40 — post-operation, in-transaction.
- After DB write; before commit.
- The record exists; can read it back.
- Can modify related records as part of the same transaction.
- Exception still rolls back.
Example use: create a child record automatically when parent is created.
Stage 50 — post-operation, out-of-transaction.
- After commit.
- Async by default.
- Failures don't affect the original operation.
- Used for external integrations (call third-party API), audit logging, notifications.
For most "after creation, do this" scenarios, this is the right stage.
Sync vs async at Stage 50.
- Sync — runs in the same operation context but outside transaction; user waits.
- Async — runs in background job queue; user doesn't wait.
For external API calls, async preferred — user shouldn't wait on third-party responsiveness.
Pre and post images.
- PreImage — snapshot of the record before the operation. Available in Stages 20, 40, 50.
- PostImage — snapshot after. Available in Stage 40 and 50.
- Required for Update operations to compare before/after.
- Configured at registration time.
The image structure: choose which columns to capture. Capturing all is heavy; capture only what you need.
Plug-in registration parameters.
- Message — Create, Update, Delete, etc.
- Primary entity — which table.
- Filtering attributes — only fire when these columns change (Update only).
- Stage.
- Execution mode — Sync / Async.
- Deployment — Server / Offline / Both.
- Unsecure / secure configuration.
Multiple plug-ins same stage.
- All run sequentially.
- Order configured via "Execution Order" on the registration.
- Lower number runs first.
For deterministic behaviour, set execution order explicitly when multiple plug-ins on same event.
Recursion control.
- Each operation has a depth value.
- A plug-in modifying its own table triggers another invocation; depth increments.
- Default max depth = 8.
- Plug-in should guard:
if (context.Depth > 1) return;
Without guard, infinite recursion possible (or hits depth limit).
Transaction scope.
- Stages 20 and 40 in transaction; all changes atomic.
- Throwing exception rolls back everything.
- Stage 50 out of transaction; original committed before this runs.
For multi-record consistency, use Stage 40; for fire-and-forget, Stage 50 async.
Performance considerations.
- Sync plug-ins block the user's operation; keep fast (< 2 seconds).
- Heavy logic belongs in async Stage 50.
- Image data load — capturing many columns slows registration; tune.
Plug-in pipeline + workflows + business rules.
- Business rules — run client-side (and server-side for some).
- Real-time workflows — run server-side, similar to plug-ins.
- Plug-ins — code.
- Async workflows / flows — out-of-band.
All can fire on the same event; ordering matters.
Common pitfalls.
- Wrong stage. Validation at Stage 50 — too late.
- No image — Update plug-in can't see old values.
- Recursive plug-in — depth guard missing.
- Slow sync plug-in — user-facing slowness.
- Stage 10 cancel on Create — record doesn't exist; cascade behaviour unclear.
- Plug-in chains — cascading effects unpredictable; debugging hard.
Best practices.
- Comment the stage choice — future maintainers understand the rationale.
- Test all stages — what happens if your plug-in throws at each stage?
- Logging in plug-ins — for production diagnosability.
- Avoid cross-table updates in Stage 20/40 unless needed.
- Keep plug-in scope narrow — one logical responsibility per plug-in.
Strategic positioning. The pipeline is the foundation of all server-side Dataverse logic. Misunderstanding it leads to subtle bugs — race conditions, partial writes, missing data. Mastering it produces robust, maintainable extensibility. Invest in the model; the productivity gain is across every plug-in you'll write thereafter.
Related guides
- Async jobs in DataverseHow Dataverse runs background work — system jobs, async plug-ins, workflow runs, and how to monitor, troubleshoot, and prevent the async backlog from getting out of hand.
- Bulk delete jobs in DataverseHow Dataverse's bulk delete handles mass record cleanup — scheduling, filters, retention policies, and the operational discipline around storage management.
- Business rules in DataverseHow business rules let you add field-level logic to forms without code — set value, lock field, show error, recommendation, and the limits of the engine.
- Business units and teams in Dataverse — a deep diveHow business units, owner teams, access teams, and Microsoft 365 group teams compose the security model in Dataverse — what each is for, how they interact, and the common design mistakes.
- Calculated and rollup columns in DataverseHow calculated columns and rollup columns work in Dataverse — what each does, the performance trade-offs, and when to use a formula column or a Power Automate flow instead.