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.

Updated 2026-09-21

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 Target entity — 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