{"id":84,"date":"2026-07-20T03:06:11","date_gmt":"2026-07-20T03:06:11","guid":{"rendered":"https:\/\/oranovasolutions.com\/blog\/?p=84"},"modified":"2026-07-20T03:06:12","modified_gmt":"2026-07-20T03:06:12","slug":"dataverse-plug-in-execution-order-and-stage-explained","status":"publish","type":"post","link":"https:\/\/oranovasolutions.com\/blog\/dataverse-plug-in-execution-order-and-stage-explained\/","title":{"rendered":"Dataverse Plug-in Execution Order and Stage Explained"},"content":{"rendered":"\n<p><\/p>\n\n\n\n<p>Dataverse plugins are one of the most powerful and common tools in the Microsoft Dynamics \/ Power Apps ecosystem. They&#8217;re the right fit when synchronous logic or server-side validation needs to take place. Think validate user input before the record can be saved&#8217; or &#8216;reliable field calculation when a user or a process updates a record&#8217;. When registering a Dataverse plug-in step, two of the most important settings are <strong>Stage of Execution<\/strong> and <strong>Execution Order<\/strong>.<\/p>\n\n\n\n<p>Here&#8217;s what they do:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Stage<\/strong> determines <em>when<\/em> the plug-in runs during the Dataverse operation.<\/li>\n\n\n\n<li><strong>Execution Order<\/strong> determines <em>which plug-in runs first<\/em> when multiple steps are registered in the same stage.<\/li>\n<\/ul>\n\n\n\n<p>Things get tricky when these concepts aren&#8217;t understood by the developer and can cause hard to diagnose problems.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Dataverse Execution Pipeline<\/h2>\n\n\n\n<p>A Dataverse operation moves through several stages.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Stage<\/th><th>Number<\/th><th>When It Runs<\/th><th>Common Uses<\/th><\/tr><\/thead><tbody><tr><td>PreValidation<\/td><td>10<\/td><td>Before the main operation, usually before the database transaction<\/td><td>Rejecting invalid input before a form can be saved<\/td><\/tr><tr><td>PreOperation<\/td><td>20<\/td><td>Before the main operation and inside the transaction<\/td><td>Changing values before they are saved (performing calculations that will exist inside the original save transaction)<\/td><\/tr><tr><td>Main Operation<\/td><td>\u2014<\/td><td>Dataverse performs the requested operation<\/td><td>Managed internally by Dataverse<\/td><\/tr><tr><td>PostOperation<\/td><td>40<\/td><td>After the main operation<\/td><td>Creating related records or performing after-save logic such as deletes<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Microsoft allows custom plug-in steps to be registered in PreValidation, PreOperation, and PostOperation. The main operation can&#8217;t be touched.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">PreValidation<\/h2>\n\n\n\n<p>Use <strong>PreValidation<\/strong> when your plug-in needs to determine whether the operation should be allowed.<\/p>\n\n\n\n<p>For example, you might prevent an opportunity from being closed when required approval records are missing. Or maybe you didn&#8217;t want to make a field required to create the Opportunity originally, but that field needs data before the opportunity can be closed. A PreValidation plugin is perfect for this.<\/p>\n\n\n\n<p>Because PreValidation occurs before the database transaction begins, cancelling the operation here is more efficient than cancelling it later and forcing Dataverse to roll back the transaction. One important exception is when the operation was triggered by another plug-in already running inside a transaction.<\/p>\n\n\n\n<p><strong>Good uses include:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Validating business rules<\/li>\n\n\n\n<li>Blocking invalid deletes<\/li>\n\n\n\n<li>Preventing unauthorized state changes<\/li>\n\n\n\n<li>Returning a clear validation message to the user<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">PreOperation<\/h2>\n\n\n\n<p>Use <strong>PreOperation<\/strong> when you need to change data before Dataverse saves the record.<\/p>\n\n\n\n<p>The <code>Target<\/code> entity contains the values being submitted. You can modify these values directly:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Entity target = (Entity)context.InputParameters&#91;\"Target\"];\n\ntarget&#91;\"new_calculatedvalue\"] = 100;<\/code><\/pre>\n\n\n\n<p>You generally should not call <code>service.Update(target)<\/code> in a PreOperation plug-in. Changing the Target directly allows Dataverse to include your changes in the original operation without starting another Update request. Microsoft specifically recommends PreOperation when changing values included in the message. This will allow the plugin to execute faster than if the same logic was run PostOperation and service.Update(target) was needed.<\/p>\n\n\n\n<p><strong>Good uses include:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Setting default values<\/li>\n\n\n\n<li>Calculating values before save<\/li>\n\n\n\n<li>Normalizing text<\/li>\n\n\n\n<li>Populating lookup fields<\/li>\n\n\n\n<li>Applying server-side business rules<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">PostOperation<\/h2>\n\n\n\n<p>Use <strong>PostOperation<\/strong> when the main Dataverse operation must finish before your logic can run.<\/p>\n\n\n\n<p>For example, after creating an account, you might create related contact records that require the new account ID.<\/p>\n\n\n\n<p>Synchronous PostOperation steps still run inside the database transaction. An exception can therefore cause the original operation and related changes to roll back. Updating the same record again during PostOperation can also trigger another Update event, potentially causing recursion.<\/p>\n\n\n\n<p><strong>Good uses include:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Creating related records<\/li>\n\n\n\n<li>Using the ID of a newly created record<\/li>\n\n\n\n<li>Performing logic that requires the completed operation<\/li>\n\n\n\n<li>Modifying output parameters returned to the caller<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Asynchronous PostOperation<\/h2>\n\n\n\n<p>Asynchronous plug-ins can only be registered in PostOperation.<\/p>\n\n\n\n<p>They are placed into the asynchronous system job queue and execute after the main record operation completes. Because they run outside the original database transaction, they cannot cancel or roll back that operation.<\/p>\n\n\n\n<p>Async plug-ins are useful for work that should not make the user wait, such as:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Sending data to an external system<\/li>\n\n\n\n<li>Performing longer-running calculations (keep in mind, we still have a 2-minute timeout on these)<\/li>\n\n\n\n<li>Creating non-critical supporting records<\/li>\n\n\n\n<li>Starting downstream processes<\/li>\n<\/ul>\n\n\n\n<p>Do not depend on multiple asynchronous plug-ins running in a particular order. Microsoft states that execution order between asynchronous plug-ins is not guaranteed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How Execution Order Works<\/h2>\n\n\n\n<p>Execution Order controls the order of steps registered for the <strong>same message and stage<\/strong>.<\/p>\n\n\n\n<p>Lower numbers run first.<\/p>\n\n\n\n<p>For example, suppose three synchronous plug-ins are registered on the Account Update message:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>PreOperation \u2014 Execution Order 10<\/li>\n\n\n\n<li>PreOperation \u2014 Execution Order 20<\/li>\n\n\n\n<li>PostOperation \u2014 Execution Order 5<\/li>\n<\/ol>\n\n\n\n<p>The execution sequence will be:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PreOperation: Order 10\nPreOperation: Order 20\nDataverse main operation\nPostOperation: Order 5<\/code><\/pre>\n\n\n\n<p>The PostOperation step does not run first just because its execution order is lower. <strong>Stage takes priority over execution order.<\/strong><\/p>\n\n\n\n<p>Execution Order only sorts plug-ins inside the same stage. Microsoft also warns that when multiple steps have the same execution-order value, their actual order is not guaranteed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Recommended Execution-Order Convention<\/h2>\n\n\n\n<p>Avoid leaving every plug-in at the default execution order.<\/p>\n\n\n\n<p>A simple convention is to leave space between steps:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>10 \u2013 Initial validation or preparation\n20 \u2013 Main custom logic\n30 \u2013 Dependent or follow-up logic<\/code><\/pre>\n\n\n\n<p>Leaving gaps makes it easier to insert another step later without renumbering everything.<\/p>\n\n\n\n<p>When one plug-in depends on another, give them distinct execution-order values and document the dependency. Better yet, avoid tightly coupling separate plug-ins unless the separation provides a clear benefit! Developers have different opinions on this, however. Having multiple smaller, simpler plugins can make it easier to debug each piece of logic. But it also means your logic is spread out across multiple registered plugins, each with its own execution order. That adds some overhead. A good rule of thumb is to be consistent with whichever route you decide to go.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Simple Rule of Thumb<\/h2>\n\n\n\n<p>Use:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>PreValidation<\/strong> to decide whether the operation should continue.<\/li>\n\n\n\n<li><strong>PreOperation<\/strong> to change the data being saved.<\/li>\n\n\n\n<li><strong>PostOperation<\/strong> when the operation must finish first.<\/li>\n\n\n\n<li><strong>Asynchronous PostOperation<\/strong> for slower or non-critical processing.<\/li>\n\n\n\n<li><strong>Execution Order<\/strong> to control sequencing within the same stage.<\/li>\n<\/ul>\n\n\n\n<p>Thanks for reading!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Dataverse plugins are one of the most powerful and common tools in the Microsoft Dynamics \/ Power Apps ecosystem. They&#8217;re the right fit when synchronous&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,6],"tags":[5,2,7,3,4],"class_list":["post-84","post","type-post","status-publish","format-standard","hentry","category-blog","category-how-to-guides","tag-dataverse","tag-dynamics-365","tag-plugins","tag-power-apps","tag-power-automate"],"_links":{"self":[{"href":"https:\/\/oranovasolutions.com\/blog\/wp-json\/wp\/v2\/posts\/84","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/oranovasolutions.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/oranovasolutions.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/oranovasolutions.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/oranovasolutions.com\/blog\/wp-json\/wp\/v2\/comments?post=84"}],"version-history":[{"count":7,"href":"https:\/\/oranovasolutions.com\/blog\/wp-json\/wp\/v2\/posts\/84\/revisions"}],"predecessor-version":[{"id":91,"href":"https:\/\/oranovasolutions.com\/blog\/wp-json\/wp\/v2\/posts\/84\/revisions\/91"}],"wp:attachment":[{"href":"https:\/\/oranovasolutions.com\/blog\/wp-json\/wp\/v2\/media?parent=84"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/oranovasolutions.com\/blog\/wp-json\/wp\/v2\/categories?post=84"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/oranovasolutions.com\/blog\/wp-json\/wp\/v2\/tags?post=84"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}