Showing results for 
Search instead for 
Did you mean: 

Best practices when customizing Power Platform and Dataverse

In my last article about Power Platform best practices, the tips covered the configuration aspect using out-of-the-box capabilities (i.e. low/no code). In this one, I touch on the best practices extending Power Platform and Dataverse with customizations. This is aimed more for pro-developers or anyone using code (e.g. C#, JavaScript). Like in the previous article, these guidelines are based on past experiences and projects, and also from common questions/mistakes seen in the community.




Brief Rationale


Choose low code/no code approach over customizations (code). This may sound obvious but it’s easy to fall back on old ways and methods that we’re conformable with. Examples:

  • Power Automate flows can handle a lot of logic that previously plugins would do
  • Use embedded canvas app or custom pages instead of custom HTML web resources
  • Business Rules as opposed to JavaScript for show/hide logic or setting field values

Adds value quicker to the business


Less maintenance effort


Easily adopt new features


More likely to have upgrade path and less likely to break with upgrades and API changes


Should server-side logic be required, leverage Azure Functions with Azure aware plugins instead of traditional plugins when (but not limited to):

  • Plugin is subject to take longer than 200ms to execute
  • Integration with external systems or APIs
  • Need to reference 1st/3rd party assemblies like KeyVault or Newtonsoft

Note: Sync operations with Azure Functions is possible with webhooks 😊


More info:

Better scalability


Can handle long running processes


Azure stack becomes available


Better monitoring


Prevent all-purpose plugins (traditional and Azure aware). Scope plugins to perform a single task based on a clear set of inputs and outputs. Select only the required columns for the step/trigger and for pre/post images. Example:


Not desirable

Desirable alternative

Single PostOperationAccountUpdate plugin which is responsible to do all post-op type of operations on accounts. Branching is done within the plugin handler.

ValidateAccountAddress which is sync on create/update of address specific columns


PopulateDefaultAccountValues which is async on create

With the 2nd approach, we have a clear idea what the plugins are doing, and we have the flexible to have different plugin step configuration for each.

Better scalability and flexibility


Better performance as async plugins can run in parallel


Better maintainability


Use Custom API instead of custom workflow activities or custom actions which also provides more flexibility.

Workflows are getting phased out


Better flexibility


Limit the data retrieved from the APIs or SDK calls by:

  • Prevent selecting all columns (Columns.All or select whole entity in Linq)
  • Use inner joins as opposed to outer
  • Omit ordering clause if not required
  • Avoid distinct if not required, try to apply the right filtering/joins instead

More info:

Better scalability and performance


Less code smells


Reference dependent web resources with the dependency feature instead of adding them to model-driven forms or ribbon.


More info:

Dependencies traceability


The system does not guarantee the load order


Better performance



Be aware of the deprecated client scripts (e.g. Xrm.Page) or messages. Recommend reviewing these under a regular frequency.

Deprecated list:


Tip: Using Solution Checker with help to detect those 😊

Better performance


More likely to have upgrade path and less likely to break with upgrades and API changes


Use the async pattern when writing JavaScript/TypeScript to reduce chaining and complexity. For example (taken from MS docs)

Xrm.WebApi.retrieveMultipleRecords("account", "?$select=name&$top=3").then(
    function success(result) {
        for (var i = 0; i < result.entities.length; i++) {
        // perform additional operations on retrieved records
    function (error) {
        // handle error conditions

Can be rewritten like this:

try {
    let result = await Xrm.WebApi.retrieveMultipleRecords("account", "?$select=name&$top=3");
    for (let entity in result.entities) {
    // perform additional operations on retrieved records
catch (error) {
    // handle error conditions

Which one is more readable?


Note: Model-driven apps now support awaiting for async OnLoad and OnSave events.

Better maintainability, cleaner code that’s easier to read


Might seem strange but do not use ExecuteMultipleRequest or ExecuteTransactionRequest in traditional plugins due to their overheard and plugin execution limit. Performance of these messages are gain more in external processes.

More info:

Better Performance


Supporting documentation and additional readings:


Hi Eric,

I don't completely agree with guideline #1.

Usage of Business Rules opposed to JS can lead to several issues:

- when the complexity increases, the readability and the maintenability of the business rules decrease a lot faster than the corrisponding JS logic.

- if you start writing logic as a BR, you can reach an asymptote where you need to throw it all away and move to JS, because of a single requirement that cannot be addressed by BR

Same goes for powerautomate. It can be written faster for easy-to-go scenarios, but in the long run they are harder to mantain.


I think that, as for every tool PowerPlatform provides us, there is a "complexity ceiling" under which is preferrable to go via codeless solutions, and above of you should directly go via full-code-based-solutions.
The hard part is to identify that ceiling...

Hi @_neronotte,

Agree that sometimes it becomes more complex and hard to scale with Business Rules than JavaScript for example, but these are just general guidelines. If the complexity is known upfront and the out-of-the-box tools aren't fit for purpose for this scenario, then looking at other custom alternatives is totally fine 🙂 Having a more senior resource would help to identify these scenarios.

Much more to learn 🙂