Where to put business logic: business rule vs client script vs plug-in vs cloud flow
Verdict: Enforce simple validation everywhere with a table business rule. Give immediate pre-save feedback in client script. Use a synchronous plug-in when a failure must roll back the triggering write. Reserve cloud flows for post-commit automation.
| Criterion | Business rule | Client script | Synchronous plug-in | Asynchronous plug-in | Cloud flow |
|---|---|---|---|---|---|
| Runs where | Table level, client and server | Browser, one app only | Server, in-transaction | Server, system job queue | Server, after commit |
| Enforced across form, Web API and flow | Yes, all access paths | No, only its own app | Yes | Yes | Yes, but reactive |
| Rolls back the triggering write | n/a | n/a | Yes, shares the transaction | No, runs outside it | No, runs outside it |
| Immediate pre-save feedback | No | Yes | No, runs on save | No | No, after commit |
| First choice for | Simple validation for non-developers | In-form UX before save | Hard transactional consistency | Slow or non-critical follow-up | Declarative post-commit automation |
Rules
- A table-level business rule enforces validation consistently across the model-driven form, Power Automate and the Web API with the least custom code.
- Only a synchronous plug-in runs within the triggering transaction, so it is the sole option when a failure must roll back the original operation.
- Consider a declarative option (business rule, workflow or cloud flow) first; escalate to a plug-in only when the declarative process cannot meet the requirement.
- For non-developers, Microsoft recommends a business rule over client script when a rule can achieve the same conditional column logic.
- To run JavaScript automatically on form load, register the library and function on the form OnLoad event, not on a ribbon button or a flow.
Traps
- A cloud flow and an asynchronous plug-in both run outside the transaction, so neither can roll back the change that triggered it.
- Client script protects only the app it is loaded into; it does not guard Web API or flow writes to the same field.