How a plugin fails — and how easily you can figure out why it failed — matters as much as its happy-path logic.
InvalidPluginExecutionException
This is the one exception type Dataverse treats specially: throwing it rolls back the entire transaction (nothing saves) and shows its message text directly to the user in a clean error dialog, instead of a generic, unhelpful platform error.
throw new InvalidPluginExecutionException("A Case cannot be closed while it still has open tasks.");
Any other exception type still aborts the operation, but surfaces as a much less friendly generic error, often without your actual message visible to the end user — so for any deliberate, expected validation failure, always throw InvalidPluginExecutionException specifically.
Tracing
The ITracingService writes diagnostic messages that get stored with the plugin’s execution trace log — the primary tool for understanding what actually happened inside a plugin run, especially in production where you can’t attach a debugger.
var tracingService = (ITracingService)serviceProvider
.GetService(typeof(ITracingService));
tracingService.Trace("Checking open tasks for Case {0}", target.Id);
tracingService.Trace(...) calls that are never actually persisted anywhere is a common early confusion — check the environment’s trace log settings before assuming your trace calls aren’t working.
Debugging locally with the Plugin Profiler
The Plugin Registration Tool includes a Profiler that can capture a real execution’s full context (target, images, configuration) and replay it against your plugin running locally in Visual Studio with a debugger attached — far more practical than guessing from trace logs alone once a bug is hard to reproduce.