Every plugin implements IPlugin, with a single Execute method as the entry point. Here’s a minimal, real example: blocking an Account from being created without a Phone number.
public class ValidateAccountPhone : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider
.GetService(typeof(IPluginExecutionContext));
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity target)
{
if (!target.Contains("telephone1") ||
string.IsNullOrWhiteSpace(target.GetAttributeValue<string>("telephone1")))
{
throw new InvalidPluginExecutionException(
"Phone number is required when creating an Account.");
}
}
}
}
Walking through it
IServiceProvider serviceProvider— the single parameter every plugin’sExecutereceives; every other service (context, tracing, organization service) is resolved from it.IPluginExecutionContext— carries everything about the current operation: which message fired, the target entity, pre/post images, depth, and more.context.InputParameters["Target"]— for Create and Update messages, this is the record being created/updated. It’s typed asobjectand needs casting toEntity.throw new InvalidPluginExecutionException(...)— the correct way to fail a plugin and show the user a clean error message, covered properly in a later chapter.
Getting the organization service
To actually query or update other records (not just inspect the Target), you need an IOrganizationService, resolved like this:
var serviceFactory = (IOrganizationServiceFactory)serviceProvider
.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
Using context.UserId runs the service call as the user who triggered the operation (respecting their security), which is almost always what you want — using a different, elevated identity is a deliberate, specific choice, not a default.
Dependency injection, by hand. Plugins don’t use a modern DI container — every dependency is manually pulled from
serviceProvider.GetService(typeof(...)). This pattern looks unusual compared to current .NET conventions, but it’s the actual, correct way the platform expects a plugin to be written.