Sam's Dynamics, Power Platform & AI Blog

Monday, 14 September 2026

Field-Level Logic in Dynamics 365 Forms: Show/Hide, Enable/Disable, and Required Levels with JavaScript

In the first post of this series, we got a form script loading and reacting to a status change. This post goes deeper into the four operations you'll use in almost every form script: showing/hiding, enabling/disabling, setting required levels, and setting values — plus when you should reach for a Business Rule instead of writing code at all.

The Four Core Operations

Every field-level customization comes down to some combination of these, called on an attribute or its control via formContext:

var formContext = executionContext.getFormContext();
var control = formContext.getControl("new_discountreason");
var attribute = formContext.getAttribute("new_discountreason");

// 1. Show or hide a field
control.setVisible(true);

// 2. Enable or disable a field
control.setDisabled(false);

// 3. Set the required level
attribute.setRequiredLevel("required"); // "none", "recommended", or "required"

// 4. Set a value
attribute.setValue("Loyalty discount");

Business Rule vs. JavaScript: When to Use Which

Business Rules are the right first choice for simple, single-form or cross-form logic — they're faster to build, easier for other admins to maintain, and don't need a developer to change later. Reach for JavaScript when you need:

  • Logic that depends on more than the handful of conditions a Business Rule can cleanly express
  • Real-time reaction to typing, not just on change/save
  • Calls to the Web API, external services, or anything Business Rules simply can't do
  • Custom UI behavior — tab/section visibility, notifications, ribbon interaction

A good rule of thumb: if you can describe the logic in one sentence with "if this field equals X, then that field is required," use a Business Rule. If you're combining three or four conditions across multiple fields, JavaScript will be far easier to read and maintain.

A Practical Example: Multi-Field Conditional Logic

Here's a script that shows a discount reason field only when a discount is applied above a threshold, and only for certain customer types — logic that would get unwieldy fast in a Business Rule:

function onLoad(executionContext) {
    var formContext = executionContext.getFormContext();
        formContext.getAttribute("new_discountpercent").addOnChange(toggleDiscountReason);
            formContext.getAttribute("new_customertype").addOnChange(toggleDiscountReason);
                toggleDiscountReason(executionContext);
                }
                
                function toggleDiscountReason(executionContext) {
                    var formContext = executionContext.getFormContext();
                        var discount = formContext.getAttribute("new_discountpercent").getValue() || 0;
                            var customerType = formContext.getAttribute("new_customertype").getValue();
                                var reasonControl = formContext.getControl("new_discountreason");
                                    var reasonAttr = formContext.getAttribute("new_discountreason");
                                    
                                        var needsReason = discount > 10 && (customerType === 1 || customerType === 2);
                                        
                                            reasonControl.setVisible(needsReason);
                                                reasonAttr.setRequiredLevel(needsReason ? "required" : "none");
                                                    if (!needsReason) {
                                                            reasonAttr.setValue(null);
                                                                }
                                                                }

Section and Tab Visibility

The same show/hide pattern applies to whole sections and tabs, which is handy for progressively revealing parts of a long form:

var tab = formContext.ui.tabs.get("tab_shipping");
tab.setVisible(true);

var section = tab.sections.get("section_shippingdetails");
section.setVisible(true);

Common Pitfalls

  • setRequiredLevel takes a string, not a boolean. Use "none", "recommended", or "required" — not true/false.
  • Disabled fields still have values. setDisabled(true) stops editing but doesn't clear the value; clear it explicitly if that's what you need.
  • Hidden required fields block save. If a field is required but hidden, users get stuck. Always pair setVisible(false) with setRequiredLevel("none").
  • Re-run your logic on load, not just on change. If a record is opened with values already set, onChange handlers won't fire automatically — call your function once during OnLoad too, as in the example above.

What's Next

Next up: working with lookups, option sets, and subgrids from JavaScript — including how to filter a lookup's results based on another field on the form.

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.

What your Copilot agent can actually see: Dataverse security when the agent is asking

What your Copilot agent can actually see

Nobody worried much about oversharing in CRM, because advanced find was tedious and nobody went looking. An agent removes that. Everything a user technically has access to is now one plain-English question away.


Your security model didn't change. What changed is that it's finally being exercised.

Identity is the whole design

Two options, and they behave completely differently.

User authentication. The signed-in user's identity flows through to Dataverse, which applies your security roles and record-level permissions as normal. The same prompt returns different answers for different people, which is exactly right.

Agent author authentication. A fixed credential does the work regardless of who asked. Microsoft scopes this to implicit or low-risk access — a weather lookup, a public phone number. If you reach for it to make something work, you've just built a bypass around your own security model.

Default to user auth. Fall back only for a genuine service-account case, like reading a shared mailbox.

Cumulative permissions get expensive

Dataverse adds up every grant from every role and every team, then checks. There's no deny.

That was survivable when access was theoretical. Now an agent will happily aggregate across everything the user can reach and summarise it in one answer. The stray owner team somebody created three years ago to solve a visibility request stops being clutter and starts being a disclosure.

Before you switch anything on: pull the role and team membership for a sample of users and check what they can actually see, not what they're supposed to see.


What the model can't protect

  • Column security profiles — salary, ID numbers, bank details. If the field is readable, the agent can read it and put it in a sentence.
  • Record sharing — invisible at design time, unauditable in bulk, and it grants exactly the access the agent will use.
  • Notes and attachments — usually wide open, frequently containing things people would never put in a proper field.

The paths that aren't in the security model at all

This is where agent projects leak. Most failures aren't a misconfigured table privilege:

  • Which connection the agent's actions run under, and whose permissions that carries
  • Whether a flow behind the agent runs as a service account with more rights than the user
  • App sharing and DLP policy governing which connectors the agent can reach
  • Any autonomous agent, which has no signed-in user at all — that identity is the ceiling on everything it can do

Give service identities a purpose-built role scoped to the specific tables they need. Not System Administrator because it was faster.

Treat retrieved content as untrusted

Records and documents are input, and input can carry instructions. A case description written by an external customer is not a safe place to take direction from.

The design rule: retrieved content informs an answer, it never authorises an action. Anything privileged runs on validated parameters through a flow or plugin, where the rules are enforced server-side and a model can't talk its way past them.

Where to start

  • Confirm every action is using user authentication unless there's a written reason not to
  • Audit team memberships and delete the ones nobody can explain
  • Column-secure the fields that would be a problem in a chat window
  • Check which identity your flows and connections actually run as
  • Test the agent as three different users, not as yourself

None of this is AI work. It's the security model you already had, finally being read out loud.

Related: Copilot in Dynamics 365 — what it actually does

Thanks for reading this article. Hope this Article will help you. Cheers!!!

#Dynamics365 #PowerPlatform #MicrosoftCopilot #DynamicsCRM #CopilotStudio #Dataverse #AIDeveloper #DynamicsAIEngineer #HireAIEngineer #AIEngineer #D365Consultant #CRMDeveloper #AIAgents #MSDyn365

Display Density in Model-Driven Apps: Less Whitespace, More CRM

 If you work with Dynamics 365, you've heard this one: "Why is there so much empty space on the screen?" Microsoft now has an ...