Every operation Dataverse performs — Create, Update, Delete, and the rest — doesn't happen in one instant step. It moves through an ordered sequence of stages, and a plugin is registered to run at exactly one of them. Understanding the order, and what's actually true at each point, is what lets you pick the right stage instead of guessing.

Quick facts
  • Stage 10 is pre-validation, before the database transaction starts
  • Stage 20 is pre-operation, inside the transaction, before the write
  • Stage 40 is post-operation, after the write has been committed
  • The execution context (IPluginExecutionContext) describes what's happening at every stage

Checkpoints in a line

Picture an airport security line: a traveler passes several checkpoints in a fixed order before boarding, and each one can stop them, wave them through unchanged, or adjust something about their trip before the next checkpoint sees them. A Dataverse record moves through its own version of that line on every operation — and a plugin is code you attach to one specific checkpoint, not the whole line.

Stage 10 — pre-validation

Pre-validation is the first checkpoint, and it runs before the database transaction has even begun. Nothing has been written yet, and no lock has been taken on the record. This makes it the right place for logic that should reject the operation outright — a security check, a business rule that says "this can never happen" — because there's nothing to roll back if your plugin throws an exception here. It's also the only stage guaranteed to run outside the transaction, which matters if you're calling something that shouldn't be tied to database locking at all.

Stage 20 — pre-operation

Pre-operation runs inside the transaction, immediately before Dataverse writes the change to the database. Your plugin can still read and modify the Target — the record being saved — and whatever you change here rides along in the same write. This is the standard stage for setting or adjusting a field on the record currently being saved: no extra database call is needed, because your change is folded into the write that's about to happen anyway.

Stage 40 — post-operation

Post-operation runs after the write has been committed. The record now exists in its final form — including anything the platform itself filled in, like a newly generated primary key. This is where you reach for logic that depends on the operation already being done: creating a related record, cascading a change to other tables, or firing off a notification. Because the write already happened, changing the original record again from here means a separate update call, not a free ride on the same transaction.

StageNameRuns whenTypical use
10Pre-validationBefore the database transaction startsOutright validation that should block the operation
20Pre-operationInside the transaction, before the writeSetting or adjusting a field on the record being saved
40Post-operationAfter the write is committedRelated-record creation, cascading updates, notifications
Example A plugin blocking the deletion of an Account with open Invoices belongs at stage 10 (pre-validation) — reject it before any work starts. A plugin that stamps a formatted reference number onto a new record belongs at stage 20 (pre-operation) — set it before the write. A plugin that creates a welcome Task once a new Contact exists belongs at stage 40 (post-operation) — it needs the Contact's real, saved ID.

The execution context

Whichever stage your plugin runs at, the platform hands it an execution context — an object implementing IPluginExecutionContext — describing exactly what's happening right now. You retrieve it out of the IServiceProvider passed into Execute. A few members you'll use constantly:

  • InputParameters — a collection of the data being submitted for this operation; on Create or Update, the record itself is here under the key Target.
  • Stage — a number telling your code which checkpoint it's currently running at (10, 20, or 40), useful if one class is registered at more than one stage.
  • MessageName — the operation being performed, such as "Create", "Update", or "Delete".
  • Depth — how many plugins deep the current chain of triggered calls already is. It increments every time a plugin's own action triggers another plugin, and the platform uses it to detect and stop infinite loops once a fixed limit is crossed.
public class ValidateOpportunityPlugin : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        var context = (IPluginExecutionContext)
            serviceProvider.GetService(typeof(IPluginExecutionContext));

        // Stage tells you which checkpoint this instance is running at
        if (context.Stage == 10 && context.MessageName == "Delete")
        {
            // pre-validation: reject before anything is written
        }
    }
}

The next chapter looks at a second, independent axis: whether a plugin runs synchronously, blocking the caller until it finishes, or asynchronously, queued to run after the response has already gone back. Stage and sync/async are separate decisions — you choose both when you register a step.

Key takeaway: Every operation passes through ordered stages — 10 (pre-validation, before the transaction), 20 (pre-operation, before the write), and 40 (post-operation, after the write). Choose the earliest stage that has the information you need: validation at 10, field changes at 20, dependent work at 40.