Low-code plugins have been in preview for more than a year. Promising an easy, modern way to implement server-side real-time logic for low-code makers, many people had high interest at the beginning and played around, but with few official announcements since then, interest seems to have waned.
However, there have been changes since the start and the functionality is getting improvements. With a learning path module now also being a part of the official preparation for the PL-200 exam, I think we can be optimistic that low-code plugins are here to stay.
In this post, I will implement a small trigger-based plugin, note changes to the original way low-code plugins worked and give my take on current issues and opportunities. We’ll focus on Dataverse for now, but keep an eye out for a post utilizing 3rd party connectors!
Problem Statement
A common requirement in CRM projects is ensuring that all phone numbers conform to a predefined format. Reasons may vary from a picky phone integration, duplicate checks or simply wanting a sense of standardization.
If the business operates across borders, country codes are especially important.
While front-end checks may mitigate the issue of mistakes in manual data entry, contacts may also be created by imports, integrations or conversions in the system itself (e.g. lead qualification). To ensure proper data quality, a server-side check needs to be implemented. In most implementations, you would use a C# plugin for such a check. Let’s see how to do this with the new low-code plugins (currently in preview)!
Implementation
All low-code plugins are created through the Dataverse Accelerator App, so that’s where we need to go. The app is restricted out of the box to users with System Administrator or System Customizer security roles, so if you don’t see the app, check first that you have the right credentials. If it still doesn’t appear, you may need to install the app through the environment administration.
Upon opening the app, you’ll choose to create either instant or automated plugins:
Instant plugins: On-demand actions callable via code, Power Fx through the Environment object, or Power Automate.
Automated plugins: Triggered by specific Dataverse events, like creating, updating, or deleting a record. Think of it like a synchronous Power Automate flow, but the only trigger is the Dataverse “When a record is created, modified or deleted” trigger (For pro coders, the plugin is registed in the usual plugin execution pipeline as part of a Microsoft assembly)
Since we want our format check to run whenever a contact is created or updated, we need an automated plugin. Choose a display name and the contact table. In advanced options, choose Pre-operation (as we want to check before committing any changes) and a solution where the plugin should be saved.
Unlike flows or classic workflows, there is no option to select “Created or Modified” (and considering there is no equivalent message for classic plugins, there likely won’t be), so two low-code plugins need to be created. Start with the option “Run this plugin when the row is Created”.
For the expression, we’re going to start with a simple regular expression check to throw an error if a contact’s business phone during creation does not conform to a common international format (You could change the regex to only match whatever format your organisation uses. ChatGPT can be very helpful here).
If(
!IsBlank(NewRecord.'Business Phone') &&
!IsMatch(NewRecord.'Business Phone', "^(\+|0)[1-9][0-9 \-\(\)\.]{7,32}$"),
Error("Business Phone should be in international format (start with +, at least 8 characters)")
);
This checks if there’s an entry for Business Phone, then validates it to ensure it begins with “+” or “0” and follows the required minimum length. If it fails, the plugin throws an error.
Most important automated plugin components/functions
OldRecord
This is the record previous to any changes, use it in Update and Delete plugins
NewRecord
This is the record after changes are applied, use it in Create and Update plugins
Set
Function to change record values, e.g. change attributes in the NewRecord in pre-operation Plugins to modify a value before saving
Error
Function to throw an error during a plugin. This error will be shown to the user in automated plug-ins. Setting an ErrorKind oder Source does not seem to have an effect, only the Message is shown.
Trace
Function to save information in the plugin trace log, which can be seen via the Plugin monitor in the Dataverse Accelerator App
With
Use the With function to set variables for the following block of code.
For the plugin which should run on update, the expression is nearly the same. There just needs to be another condition added to check that the value of business phone has been changed.
Currently, there is no option for low-code plugins to run only when specific fields are updated (we don’t want to throw an error regarding the phone number when some integrations updates a contacts credit limit).
If(
!IsBlank(NewRecord.'Business Phone') &&
!(OldRecord.'Business Phone' = NewRecord.'Business Phone') &&
!IsMatch(NewRecord.'Business Phone', "^(\+|0)[1-9][0-9 \-\(\)\.]{7,32}$"),
Error("Business Phone should start be in international format (start with +)")
);
This is already a functional plugin, avoiding several possible data entry errors. To show how to update a value during pre-operation, we will now change the code so that any starting 0 is replaced with the country code for Germany (Let’s just say there is a clear business requirement where we always want a specific international code at the beginning and only german numbers would be input with a starting 0).
If(
!IsBlank(NewRecord.'Business Phone') &&
!(OldRecord.'Business Phone' = NewRecord.'Business Phone') &&
!IsMatch(NewRecord.'Business Phone', "^(\+|0)[1-9][0-9 \-\(\)\.]{7,32}$"),
Error("Business Phone should start be in international format (start with 0 or +)")
);
If(
StartsWith(NewRecord.'Business Phone', "0"),
Set(NewRecord.'Business Phone', Substitute(NewRecord.'Business Phone',"0", "+49",1))
);
Using a second condition block running after our initial check, we substitute a starting 0 with +49. To replace a value to be updated, the field needs be changed with the Set function in the NewRecord object.
Now we will always have a phone number in the format we want.
Benefits and Issues
Low-code plugins have a very low barrier to entry, similar to classic workflows, while enabling far more freeform logic. Connections can be used easily without coding skills, which until now has only been possible with power automate in an asynchronous way. Power Fx will only become easier to write with improved copilot implementation, which Microsoft is focusing quite a bit on.
However, there are currently quite a few issues and I’m not sure how many are caused by preview.
- Triggers: Currently limited to Create, Update, and Delete. More messages to register are coming (documentation is already talking about Read message), but with intellisense needing to be supported, this will be bit by bit.
- Field-specific Triggers: No option yet to run plugins only on changes to specific fields.
- No Async Support: Expected to be extended.
- Plugin Execution Order: Considering other low-code tools, I’m not sure if MS is going to show this complexity in the frontend.
- Governance: There doesn’t seem to be a documented way to create/include low-code plugins in a VS Solution file. However, since pac power-fx commands are coming and the plugins in the unpacked solution consist of a json file for registration and a yaml file for the powerfx expression itself, I hope that that’s coming as well.
- Performance: According to tests by Riccardo Gregori, low-code plugins are noticably (20-25 %) slower than C# plugins. When implementing synchronous automation, every bit counts since this is dead time for the user interface. However, according to several blogs, a part of this inefficiency may be based on “lazy” conversion of powerfx to C# (Filter after data retrieval, loops instead of executemultiple…), so there can be hope.
Conclusion
Low-code plugins open new possibilities to create custom server-side logic, including even external systems without needing more advanced coding skills. They are suited to low-code makers who until now had to rely on classic workflow to handle any kind of synchronous logic.
I’m optimistic that many issues regarding the plugin registration and performance will be improved overtime, but less so that there is going to be a usable instrument for version control. Maybe with the announced Source Code Integration, there will be changes for this area as well.
If this doesn’t happen, I suspect automated low-code plugins will be handled like Business Rules & Javascript are currently; customers without strict standards will have a mix of pro-code plugins, low-code plugins and workflows, while others (and likely many consultancies) will implement restrictions which err on the side of ‘Use C# plugins for everything, even if it takes a bit longer, so version control works and we have an overview of all the custom logic in one place’. That said, the flexibility, ease of use and integration with connectors are likely to drive adoption for straightforward use cases in many organizations.