Sam's Dynamics, Power Platform & AI Blog

Monday, 14 September 2026

Getting Started with JavaScript in Dynamics 365 CRM Forms: A Step-by-Step Guide

If you've spent any time around the Dynamics 365 / Power Platform world lately, it's all Copilot, agents, and AI everywhere. But underneath most of that AI layer, the forms your users actually work in still run on plain old client-side JavaScript. It's still the most reliable way to control field behavior, run validations, and react to what a user is doing in real time — and it's a skill every CRM developer should be solid on. This is the first post in a short series on form scripting, working up from the basics to where it starts overlapping with Copilot Studio agents.

Why JavaScript Still Matters

Business Rules and Power Automate cover a lot of ground now, but there are still things only JavaScript does well: instant field-level reactions as a user types, custom validation logic that's too complex for a Business Rule, and dynamic UI changes (hiding sections, changing option sets, enabling/disabling controls) based on more than one condition at once. If you're building anything non-trivial on a form, you'll end up here eventually.

What You'll Need

  • A Dynamics 365 (model-driven app) environment with System Customizer or System Administrator access
  • A basic understanding of JavaScript (variables, functions, conditionals)
  • Browser developer tools (F12) for testing and debugging

Step 1: Create the JavaScript Web Resource

In the Power Apps maker portal, go to your solution and select New → More → Web resource. Choose type Script (JScript), upload or paste your .js file, and give it a clear naming convention, e.g. new_/forms/account_form.js. Save and publish.

Step 2: Add the Library to the Form

Open the form in the form editor, go to the Form Libraries section, and add the web resource you just created. This makes its functions available to the form's event handlers.

Step 3: Understand formContext Basics

Every modern form script receives an executionContext, from which you get the formContext — your entry point to everything on the form. Avoid the old Xrm.Page pattern; it's deprecated. Here's the core API you'll use constantly:

function onLoad(executionContext) {
    var formContext = executionContext.getFormContext();
    
        // Get an attribute (field)
            var statusAttr = formContext.getAttribute("statuscode");
            
                // Get a control (the UI element for that field)
                    var reasonControl = formContext.getControl("new_reasoncode");
                    
                        // Read and write values
                            var currentValue = statusAttr.getValue();
                                statusAttr.setValue(1);
                                }

Step 4: Register the OnLoad and OnChange Events

In the form editor, select the form's properties (or a specific field's event handlers) and register your function against the OnLoad or OnChange event. Here's a working example that shows/hides and requires a reason field based on status:

function onLoad(executionContext) {
    var formContext = executionContext.getFormContext();
        var statusAttr = formContext.getAttribute("statuscode");
            if (statusAttr) {
                    statusAttr.addOnChange(onStatusChange);
                            onStatusChange(executionContext); // run once on load too
                                }
                                }
                                
                                function onStatusChange(executionContext) {
                                    var formContext = executionContext.getFormContext();
                                        var status = formContext.getAttribute("statuscode").getValue();
                                            var reasonControl = formContext.getControl("new_reasoncode");
                                                var reasonAttr = formContext.getAttribute("new_reasoncode");
                                                
                                                    if (status === 2) {
                                                            reasonControl.setVisible(true);
                                                                    reasonAttr.setRequiredLevel("required");
                                                                        } else {
                                                                                reasonControl.setVisible(false);
                                                                                        reasonAttr.setRequiredLevel("none");
                                                                                            }
                                                                                            }

Step 5: Add Save Validation

Register a function on the form's OnSave event to block a save when something's wrong — useful for checks that are too specific for a Business Rule:

function onSave(executionContext) {
    var formContext = executionContext.getFormContext();
        var emailAttr = formContext.getAttribute("emailaddress1");
            var email = emailAttr ? emailAttr.getValue() : null;
            
                if (email && email.indexOf("@") === -1) {
                        executionContext.getEventArgs().preventDefault();
                                formContext.ui.setFormNotification(
                                            "Please enter a valid email address before saving.",
                                                        "ERROR",
                                                                    "email_validation"
                                                                            );
                                                                                } else {
                                                                                        formContext.ui.clearFormNotification("email_validation");
                                                                                            }
                                                                                            }

Common Pitfalls

  • Forgetting to pass executionContext. When registering an event handler in the form editor, make sure "Pass execution context as first parameter" is checked.
  • Library load order. If one script depends on functions in another, the dependent library needs to be added after it in the Form Libraries list.
  • Null reference errors. Always check an attribute or control exists before calling methods on it — fields aren't always on every form.
  • Testing only in the maker portal. Always test in the actual app the field workers use; form customizations can differ across apps.

What's Next in This Series

This is post one of five on form scripting. Coming up:

  • Field-level logic in depth: show/hide, enable/disable, required levels, and when to use a Business Rule instead
  • Working with lookups, option sets, and subgrids from JavaScript
  • Calling the Dataverse Web API from form scripts to read and update related records
  • From script to agent: wiring a Copilot Studio agent action into a JavaScript-driven ribbon button

Form scripting isn't going away just because agents are getting smarter — if anything, knowing both is what makes a CRM developer valuable right now. Next post picks up with field-level logic in more depth.

No comments:

Post a Comment

Packaging and Deploying Web Resources: A Practical ALM Workflow for Dynamics 365 JavaScript

You've written the script. You've unit-tested the logic and stepped through it in dev tools. It works perfectly on your form. Now co...