5.1 Plug-in Fundamentals & the Event Execution Pipeline Stages
Key Takeaways
- A plug-in is a .NET class library that implements IPlugin and runs inside the Dataverse event execution pipeline in response to a message such as Create or Update.
- The pipeline has four stages: Pre-validation (stage 10), Pre-operation (stage 20), Main operation (stage 30, reserved for the platform), and Post-operation (stage 40).
- Pre-validation runs outside the database transaction and before the security privilege check; Pre-operation and Post-operation run inside the transaction.
- Only Post-operation (stage 40) plug-ins can run asynchronously; Pre-validation and Pre-operation must run synchronously because they gate the core operation.
- Choosing the correct stage and mode is a technical-design decision as much as a coding decision -- it determines whether your logic can block bad data, see committed changes, or run without slowing down the user.
Every create, update, delete, or retrieve against Dataverse -- whether it originates from a model-driven app form save, a Power Automate flow, the Web API, or another plug-in -- passes through the same processing pipeline before Dataverse commits it to the database. Understanding that pipeline, and where a plug-in sits inside it, is the foundation for everything else in Domain 5 of PL-400: business logic in plug-ins, custom APIs, business events, even integration patterns all build on this model.
What a Plug-in Is
A plug-in is a class in a compiled .NET assembly (a DLL) that implements the SDK's IPlugin interface:
public interface IPlugin
{
void Execute(IServiceProvider serviceProvider);
}
You register that assembly with Dataverse (via the Plug-in Registration Tool -- covered in 5.4), and Dataverse invokes Execute whenever a matching message occurs against a matching table. A message is the SDK name for an operation: Create, Update, Delete, Retrieve, RetrieveMultiple, Assign, SetState, Associate, Disassociate, and custom messages you define yourself (5.5). Every plug-in step you register is a subscription to one message, on one table (or all tables, for a handful of messages), at one pipeline stage.
The Event Execution Pipeline
Dataverse processes every message through the same ordered pipeline of stages, whether or not any custom plug-in is registered on it:
| Stage number | Stage name | Runs inside DB transaction? | Who can register here |
|---|---|---|---|
| 10 | Pre-validation | No | Custom plug-ins (sync only) |
| 20 | Pre-operation | Yes | Custom plug-ins (sync only) |
| 30 | Main operation | Yes | Platform core only (not partner code) |
| 40 | Post-operation | Yes if sync, no if async | Custom plug-ins (sync or async) |
Pre-validation (10) is the earliest point a plug-in can intercept a request. It runs before the platform checks the calling user's security privileges and before the database transaction opens. This makes it the right stage for logic that should reject bad input as cheaply as possible, before Dataverse spends any effort on the request -- but it also means a pre-validation plug-in cannot assume the user is authorized to perform the operation, and it cannot see data written by other plug-ins in the same transaction, because no transaction exists yet.
Pre-operation (20) runs after the privilege check but before Dataverse's core platform logic executes. The database transaction is already open, so a pre-operation plug-in can modify the incoming data (through InputParameters, covered in 5.2) and those changes flow into the main operation as if the caller had sent them -- this is the standard stage for defaulting or correcting field values before they're saved.
Main operation (30) is where Dataverse's own platform code performs the actual database write. Partner and ISV plug-ins cannot register here; it's reserved for Microsoft's internal system operations. You'll see it referenced on the exam mainly so you can rule it out as a registration option.
Post-operation (40) runs after the core operation completes. This is the only stage available for asynchronous execution, and it's the stage most business logic uses -- because by the time a post-operation plug-in runs, the record has already been created or updated (with a real GUID, for Create), and any other synchronous plug-ins on earlier stages have already run. If the step is registered as synchronous, it's still inside the same database transaction as the triggering operation: throwing an exception here rolls back everything, including the main operation.
Synchronous vs. Asynchronous Execution
Synchronous plug-ins run in-line with the user's request, inside the same transaction. The caller waits for them to finish, and the caller's operation fails (with the plug-in's error shown to the user) if the plug-in throws. Synchronous plug-ins are only valid at stages 10, 20, and 40.
Asynchronous plug-ins are queued as a system job and executed later by a background service. The caller's request returns immediately without waiting. Asynchronous execution is only available at stage 40 (post-operation) -- it wouldn't make sense at pre-validation or pre-operation, since those stages exist specifically to influence or block an operation that hasn't happened yet, and an async job can't do that after the fact. Use async for logic that's slow (calling an external API), non-critical to the immediate transaction (sending a notification), or that doesn't need to block the user from seeing their save succeed.
Picking the Right Stage: A Design Decision
The PL-400 exam frequently tests this as a scenario question rather than a memorization question: given a requirement ("block the save if X," "recalculate a value after the record commits," "call a slow external service without delaying the user"), you're expected to name the correct stage and mode. As a rule of thumb: validation and blocking logic belongs in pre-validation or pre-operation (synchronous); logic that depends on the committed record, or that touches other tables/systems, belongs in post-operation (synchronous if it must still block a related process, asynchronous if it doesn't). Later sections build directly on this: 5.2 covers how a plug-in reads and writes data once it's executing inside a chosen stage, and 5.3 covers how pre- and post-images let a plug-in see data from before and after the core operation.
A developer needs a plug-in that can prevent an Update from being saved if a required field is blank, and should run as cheaply as possible, before any privilege check occurs. Which stage and mode should the plug-in register at?
Put these Dataverse event execution pipeline stages in the order they execute, from first to last:
Arrange the items in the correct order