Every Dataverse operation (Create, Update, Delete, etc.) passes through a defined pipeline of stages, and a plugin is registered to run at exactly one of them. Picking the right stage is as important as the plugin’s actual logic.

The stages

  • Pre-validation — runs before the main platform validation and before the database transaction begins. Even a security-check failure further down won’t have happened yet. Rarely used for business logic; mostly for very early, cheap checks.
  • Pre-operation — runs inside the database transaction, after validation, but before the core platform operation (the actual insert/update) executes. This is where you can still modify the incoming data before it’s saved — the most common stage for validation and data manipulation.
  • Main operation — reserved for Microsoft’s own core platform logic; custom plugins cannot register here.
  • Post-operation — runs after the core operation has completed and the record is saved. The data already exists in its new state; this is where you react to a completed change (update related records, trigger integrations, send notifications).
The question that determines your stage. Do you need to change the data before it saves? Use Pre-operation. Do you need to react after it’s already saved, often touching other records? Use Post-operation. Getting this backwards is one of the most common real plugin bugs — for example, trying to modify the triggering record’s own fields in Post-operation, which technically works but requires an extra, avoidable round-trip update instead of just setting the value in Pre-operation before the single save happens.

Depth and recursion

The pipeline tracks a depth value, incrementing each time a plugin triggers another operation that runs more plugins. This is what lets you guard against infinite recursion (a plugin on Update that itself updates the record, re-triggering itself) by checking context.Depth and exiting early past a sensible threshold.