HomeBlogDevelopment & IntegrationsMicrosoft Partner · Charlottesville, VA
Development & Integrations

Dynamics 365 JavaScript Examples: A Developer Guide

Practical Dynamics 365 JavaScript examples using the current Client API: formContext, Xrm.WebApi CRUD, event handlers, and field validation. Updated for 2026.

Dynamics 365 GroupJune 11, 202514 min read← All posts
Dynamics 365 JavaScript Examples: A Developer Guide

TL;DR: The Dynamics 365 Client API centers on formContext (retrieved from executionContext) and Xrm.WebApi for data operations. The older Xrm.Page namespace is deprecated. The examples below use the current supported API surface. All scripts run as JavaScript web resources attached to form events.

What Is the Dynamics 365 JavaScript Client API?

The Dynamics 365 Client API gives developers a supported way to interact with model-driven app forms using JavaScript. According to Microsoft Learn’s Client API reference (retrieved June 2026), the API exposes the Xrm namespace, formContext, and Xrm.WebApi as the three primary surfaces for client-side scripting.

I work with this API on nearly every Dynamics 365 engagement. The key shift since 2019 is that Xrm.Page is deprecated. You now retrieve formContext from the executionContext parameter that Dynamics 365 passes to your function. Missing this distinction is the most common source of broken scripts I see on client projects. If you are configuring the wider system around these scripts, my guide on how to customize Dynamics 365 CRM covers the form and entity setup that scripts depend on.

The Xrm.Page object was deprecated in an update released in 2019. Microsoft recommends migrating to formContext retrieved via executionContext.getFormContext() (Microsoft Learn - Deprecated client APIs, retrieved June 2026).

How Do You Set Up a JavaScript Web Resource in Dynamics 365?

Every JavaScript customization in Dynamics 365 lives in a web resource. Microsoft Learn documents the full process at Create or edit web resources (retrieved June 2026). The setup takes four steps: write the script, upload it as a web resource, add it to the form, and register the event handler.

Here’s the environment I use on every project:

  • Visual Studio Code with the Power Platform Tools extension for syntax support
  • Git for version control on all web resource files
  • Power Platform CLI (pac) for pushing web resources without manual UI uploads
  • A sandbox environment for testing before publishing to production

If you do not have a non-production environment yet, see how to access a Dynamics 365 sandbox before you start writing scripts.

Attaching a Script to a Form Event

Once your JavaScript file is uploaded as a web resource, you attach it to a form through the form editor. Navigate to Settings > Customizations > Customize the System, open the entity form, and go to Form Properties. Add your web resource under the Event Library tab, then register specific functions against events like OnLoad, OnSave, or field-level OnChange.

Your function signature must accept executionContext as the first parameter. Dynamics 365 passes this object automatically when the event fires.

// Correct function signature for a form event handler
function onFormLoad(executionContext) {
  var formContext = executionContext.getFormContext();
  // All form interactions go through formContext, not Xrm.Page
}

Working with formContext: Getting and Setting Field Values

The formContext object is the supported replacement for the deprecated Xrm.Page. Microsoft Learn documents the full attribute and control methods at the formContext.data.entity reference (retrieved June 2026). Every field interaction follows the same pattern: get the attribute, then call the appropriate method.

In my experience, the most common runtime errors come from calling getAttribute() on a field that isn’t on the current form view. I always add a null check before accessing attribute methods.

Reading Field Values

function readFieldValues(executionContext) {
  var formContext = executionContext.getFormContext();

  // Text field
  // null-check omitted here for brevity - see the safeGetValue helper in Troubleshooting
  var accountName = formContext.getAttribute("name").getValue();

  // Lookup field (returns array or null)
  var ownerLookup = formContext.getAttribute("ownerid").getValue();
  var ownerId = ownerLookup ? ownerLookup[0].id : null;
  var ownerName = ownerLookup ? ownerLookup[0].name : null;

  // Option set (returns integer)
  var statusCode = formContext.getAttribute("statuscode").getValue();

  // Date field
  var closeDate = formContext.getAttribute("estimatedclosedate").getValue();
}

Setting Field Values

function setFieldValues(executionContext) {
  var formContext = executionContext.getFormContext();

  // Text or number
  formContext.getAttribute("description").setValue("Updated by script");

  // Option set (pass the integer value)
  formContext.getAttribute("prioritycode").setValue(2);

  // Lookup field (must pass an array of objects)
  formContext.getAttribute("primarycontactid").setValue([
    {
      id: "{00000000-0000-0000-0000-000000000001}",
      name: "Jane Smith",
      entityType: "contact"
    }
  ]);
}

Showing, Hiding, and Locking Controls

function adjustFormControls(executionContext) {
  var formContext = executionContext.getFormContext();

  // Hide a field
  formContext.getControl("new_internalcode").setVisible(false);

  // Make a field read-only
  formContext.getControl("name").setDisabled(true);

  // Set required level: "none", "recommended", or "required"
  formContext.getAttribute("emailaddress1").setRequiredLevel("required");

  // Show an inline field notification
  formContext.getControl("emailaddress1").setNotification(
    "A valid email is required before saving.",
    "emailValidation"
  );

  // Clear the notification
  formContext.getControl("emailaddress1").clearNotification("emailValidation");
}

How Does Xrm.WebApi Work for CRUD Operations?

The Xrm.WebApi interface handles all data operations from client-side JavaScript. It sits on top of the Dataverse Web API and returns JavaScript Promises. Microsoft Learn documents it fully at Xrm.WebApi reference (retrieved June 2026). Every method returns a Promise, so you handle results with .then() / .catch() or async/await. It supports create, retrieve, update, delete, and execute operations. The online version (Xrm.WebApi.online) runs under the current user’s security context.

Create a Record

async function createRelatedTask(executionContext) {
  var formContext = executionContext.getFormContext();
  var accountId = formContext.data.entity.getId();

  var taskRecord = {
    subject: "Follow-up call",
    scheduledend: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days out
    "regardingobjectid_account@odata.bind": "/accounts(" + accountId.replace(/[{}]/g, "") + ")"
  };

  try {
    var result = await Xrm.WebApi.online.createRecord("task", taskRecord);
    Xrm.Navigation.openAlertDialog({ text: "Task created: " + result.id });
  } catch (error) {
    console.error("Error creating task:", error.message);
  }
}

Retrieve a Single Record

async function getAccountDetails(accountId) {
  var options = "?$select=name,telephone1,emailaddress1,revenue";

  try {
    var record = await Xrm.WebApi.online.retrieveRecord("account", accountId, options);
    console.log("Account name:", record.name);
    console.log("Phone:", record.telephone1);
    return record;
  } catch (error) {
    console.error("Retrieve failed:", error.message);
  }
}

Retrieve Multiple Records with OData Filter

async function getOpenOpportunities(accountId) {
  var cleanId = accountId.replace(/[{}]/g, "");
  var options = [
    "?$select=name,estimatedvalue,estimatedclosedate",
    "&$filter=_parentaccountid_value eq " + cleanId + " and statecode eq 0",
    "&$orderby=estimatedclosedate asc",
    "&$top=50" // always cap the result set to avoid unbounded queries
  ].join("");

  try {
    // The third argument caps records per page; $top in the query caps the total
    var result = await Xrm.WebApi.online.retrieveMultipleRecords("opportunity", options, 50);
    return result.entities; // array of record objects
  } catch (error) {
    console.error("Query failed:", error.message);
    return [];
  }
}

Update a Record

async function updateRecordStatus(entityName, recordId, updateData) {
  try {
    await Xrm.WebApi.online.updateRecord(entityName, recordId, updateData);
    console.log("Record updated successfully");
  } catch (error) {
    console.error("Update failed:", error.message);
  }
}

// Usage:
await updateRecordStatus("account", accountId, { telephone1: "555-1234", revenue: 500000 });

Delete a Record

async function deleteRecord(entityName, recordId) {
  try {
    await Xrm.WebApi.online.deleteRecord(entityName, recordId);
    console.log("Record deleted");
  } catch (error) {
    console.error("Delete failed:", error.message);
  }
}

After a create or update, you often need to reload the form to show server-calculated values. See how to refresh a form using JavaScript in Dynamics 365 for the supported formContext.data.refresh() pattern.

How Do You Handle Form Events in Dynamics 365 JavaScript?

Dynamics 365 fires events at predictable points in the form lifecycle. Microsoft Learn documents the full event model at Events in forms and grids (retrieved June 2026). The three events you’ll use most often are OnLoad, OnSave, and field-level OnChange.

Across recent projects, I lean on these three events for distinct jobs. OnLoad sets initial form state and defaults. Field OnChange drives conditional visibility and required-level rules. OnSave runs pre-save validation and can cancel the save.

OnLoad: Setting Initial Form State

function onAccountFormLoad(executionContext) {
  var formContext = executionContext.getFormContext();

  // Check the form type: 1=Create, 2=Update, 3=Read-only, 4=Disabled
  var formType = formContext.ui.getFormType();

  if (formType === 1) {
    // New record - set defaults
    formContext.getAttribute("new_source").setValue("Web");
    formContext.getControl("new_legacyid").setVisible(false);
  }
}

OnChange: Conditional Field Logic

function onCustomerTypeChange(executionContext) {
  var formContext = executionContext.getFormContext();
  var customerType = formContext.getAttribute("customertype").getValue();

  // Show company name only for business accounts
  var isBusinessAccount = (customerType === 1);
  formContext.getControl("parentaccountid").setVisible(isBusinessAccount);
  formContext.getAttribute("parentaccountid").setRequiredLevel(
    isBusinessAccount ? "required" : "none"
  );
}

OnSave: Validating Before Save

// Helper: returns null instead of throwing when the field is off the form.
function safeGetValue(formContext, fieldName) {
  var attribute = formContext.getAttribute(fieldName);
  return attribute ? attribute.getValue() : null;
}

function onOpportunitySave(executionContext) {
  var formContext = executionContext.getFormContext();
  var saveEvent = executionContext.getEventArgs();

  var closeDate = safeGetValue(formContext, "estimatedclosedate");
  var revenue = safeGetValue(formContext, "estimatedvalue");

  if (!closeDate) {
    formContext.getControl("estimatedclosedate").setNotification(
      "Close date is required before saving.",
      "closeDateRequired"
    );
    saveEvent.preventDefault(); // Cancel the save
    return;
  }

  if (revenue && revenue > 100000) {
    // Large deal: auto-assign to senior rep logic here
    console.log("High-value opportunity flagged for senior review.");
  }
}

How Do You Execute Workflows and Custom Actions via JavaScript?

You can trigger Dataverse workflows and custom API actions from JavaScript using Xrm.WebApi.online.execute(). Microsoft Learn documents the request object shape at Xrm.WebApi.online.execute (retrieved June 2026). The key is constructing the request object with the correct getMetadata() method.

Execute a Workflow on Demand

async function triggerWorkflow(executionContext) {
  var formContext = executionContext.getFormContext();
  var entityId = formContext.data.entity.getId().replace(/[{}]/g, "");
  var workflowId = "YOUR-WORKFLOW-GUID-HERE"; // Replace with actual GUID

  var executeWorkflowRequest = {
    entity: {
      id: entityId,
      entityType: "account"
    },
    EntityId: entityId,
    WorkflowId: workflowId,
    getMetadata: function() {
      return {
        boundParameter: "entity",
        parameterTypes: {
          entity: { typeName: "mscrm.account", structuralProperty: 5 },
          EntityId: { typeName: "Edm.Guid", structuralProperty: 1 },
          WorkflowId: { typeName: "Edm.Guid", structuralProperty: 1 }
        },
        operationType: 0,
        operationName: "ExecuteWorkflow"
      };
    }
  };

  try {
    await Xrm.WebApi.online.execute(executeWorkflowRequest);
    Xrm.Navigation.openAlertDialog({ text: "Workflow triggered successfully." });
  } catch (error) {
    console.error("Workflow execution failed:", error.message);
  }
}

Call a Custom API Action

async function callCustomAction(accountId, customParameter) {
  var request = {
    Target: {
      accountid: accountId.replace(/[{}]/g, ""),
      "@odata.type": "Microsoft.Dynamics.CRM.account"
    },
    CustomParameter: customParameter,
    getMetadata: function() {
      return {
        boundParameter: "Target",
        parameterTypes: {
          Target: { typeName: "mscrm.account", structuralProperty: 5 },
          CustomParameter: { typeName: "Edm.String", structuralProperty: 1 }
        },
        operationType: 0,
        operationName: "new_YourCustomActionName"
      };
    }
  };

  try {
    var result = await Xrm.WebApi.online.execute(request);
    if (result.ok) {
      var responseBody = await result.json();
      console.log("Custom action result:", responseBody);
    }
  } catch (error) {
    console.error("Custom action failed:", error.message);
  }
}

Custom actions and workflows often pair with server-side logic. If you need a plugin behind one of these actions, my walkthrough on how to register a plugin in Dynamics 365 covers the Plugin Registration Tool steps.

What Are the Performance and Security Rules for Client Scripts?

Microsoft’s Power Apps component model documentation (retrieved June 2026) identifies excessive synchronous XHR calls and blocking form loads as the primary causes of poor form performance. Client scripts run in the user’s browser, which means every unnecessary API call directly adds latency to the form experience.

Performance Rules I Follow

  • Use async/await or Promises for all Web API calls. Never use synchronous XHR.
  • Consolidate multiple retrieveRecord calls into a single retrieveMultipleRecords with $select to limit columns.
  • Avoid loading scripts on forms where they are not needed. Register web resources to specific forms, not globally.
  • Cache repeated lookups in form-scoped variables during OnLoad rather than re-querying on each OnChange.
  • Keep OnLoad handlers lean. Defer non-critical logic to field events or lazy loading.

Security Rules I Follow

Client-side JavaScript runs in the user’s browser and executes under the current user’s security context. This is important. It means:

  • Never embed API keys, passwords, or connection strings in JavaScript web resources. They are visible to any user who can open DevTools.
  • Use server-side plugins or Azure Functions for logic that requires elevated permissions or must remain private.
  • Validate all data server-side. Client validation improves UX but does not enforce business rules.
  • Always sanitize values before inserting them into the DOM if you use HTML web resources alongside your scripts.

A related performance task is reloading subgrids after a Web API write. See how to refresh a subgrid in Dynamics 365 with JavaScript for the getControl().refresh() approach.

Common Troubleshooting Patterns

Most JavaScript errors in Dynamics 365 fall into a small set of categories. Here are the patterns I check first on any broken script.

Script Not Firing

The function is registered in form properties but doesn’t run. Check these in order:

  1. The function name in the event registration must match exactly (case-sensitive) the function name in your JavaScript file.
  2. The web resource must be added to the Event Library on the form before the function can be registered.
  3. Publish all customizations after making changes. Unpublished web resources run the old code.
  4. Open browser DevTools (F12), navigate to the Sources tab, and search for your web resource file name to confirm the browser loaded the updated version.

Cannot read property of null Errors

This almost always means getAttribute() returned null because the field isn’t on the form, or the field logical name has a typo. Here is the same safeGetValue helper used earlier, expanded with a console warning to make missing fields easy to spot:

function safeGetValue(formContext, fieldName) {
  var attribute = formContext.getAttribute(fieldName);
  if (!attribute) {
    console.warn("Attribute not found on form:", fieldName);
    return null;
  }
  return attribute.getValue();
}

Debugging with Browser DevTools

Chrome and Edge DevTools both work with Dynamics 365. I use:

  • Console tab: console.log() statements at key points to trace execution flow
  • Sources tab: Set breakpoints directly in your web resource file
  • Network tab: Monitor Web API calls and their responses
  • debugger; statement inline in your code triggers a breakpoint automatically when DevTools is open

Common Error Types and Root Causes

Error / Symptom Most Likely Cause First Fix to Try
Script does not run Function not registered, or customizations not published Re-check event registration and publish all
Cannot read property of null getAttribute() returned null (field off form or typo) Null-check the attribute before calling methods
Web API call rejects Bad OData filter syntax or wrong logical name Inspect the request URL in the Network tab
execute action fails Wrong typeName or structuralProperty in metadata Match parameter types to the action definition
Old code keeps running Browser cached the previous web resource Hard-refresh and re-publish the web resource

How Does JavaScript Fit with Plugins and Power Automate?

JavaScript handles the presentation layer: what the user sees and interacts with on a form. Plugins and Power Automate handle server-side logic and cross-entity automation. Knowing which to use saves significant debugging time.

Layer Technology When to Use
Form UI JavaScript web resource Field visibility, real-time validation, UI updates
Server-side sync C# Plugin Business rule enforcement, pre/post operation logic
Async automation Power Automate flow Cross-system workflows, email, approvals
Data API calls Xrm.WebApi Reading/writing Dataverse data from client scripts

A common pattern I use: JavaScript validates data client-side and provides immediate feedback. A plugin enforces the same rule server-side, so it can’t be bypassed. Power Automate handles the downstream notifications and integrations after save.

For deeper integration work, our Dynamics 365 customization and development services covers both client-side and server-side implementation.

FAQ

What is the difference between Xrm.Page and formContext in Dynamics 365 JavaScript?

Xrm.Page is the deprecated client API from earlier versions of Dynamics 365. Microsoft recommends using formContext, which you retrieve by calling executionContext.getFormContext() inside your function (Microsoft Learn - Deprecated client APIs, retrieved July 2026). Static Xrm.Page access remains supported for backward compatibility, and Microsoft says it will not be removed as soon as some other deprecated client API methods. Use the newer form-context approach where possible; do not treat the deprecation as a published removal date for Xrm.Page.

How do I get a field value in Dynamics 365 JavaScript using the current API?

Use formContext.getAttribute("fieldlogicalname").getValue() where formContext is retrieved from executionContext.getFormContext(). Always add a null check on the attribute before calling .getValue(), because the attribute returns null if the field is not present on the current form view.

How do I call the Dynamics 365 Web API from JavaScript without a token?

Xrm.WebApi.online handles authentication automatically using the current user’s session. You do not need to manage OAuth tokens for in-form scripts. Pass the entity logical name and record ID directly to methods like createRecord, retrieveRecord, updateRecord, and deleteRecord.

Can I use async/await in Dynamics 365 JavaScript web resources?

Yes. Modern Dynamics 365 runs in Chromium-based browsers (Edge and Chrome) that fully support ES2017 async/await. Using async/await is the recommended pattern for Xrm.WebApi calls because it produces cleaner code than chained .then() callbacks and handles errors naturally with try/catch.

How do I prevent a form save in Dynamics 365 JavaScript?

In your OnSave event handler, call executionContext.getEventArgs().preventDefault() to cancel the save. Show a field notification first so the user knows why the save was blocked. Note: this only prevents the client-side save. It does not prevent saves triggered by plugins or direct API calls.


Frequently Asked Questions

What is the difference between Xrm.Page and formContext in Dynamics 365 JavaScript?

`Xrm.Page` is the deprecated client API from earlier Dynamics 365 versions. Use `formContext` instead, retrieved via `executionContext.getFormContext()` inside your event handler function. Microsoft says static `Xrm.Page` access remains supported for backward compatibility and encourages the newer form-context approach where possible (Microsoft Learn, retrieved July 2026).

How do I get a field value in Dynamics 365 JavaScript using the current API?

Use `formContext.getAttribute("fieldlogicalname").getValue()` where `formContext` comes from `executionContext.getFormContext()`. Always null-check the attribute before calling `.getValue()` — the attribute returns null if the field is not on the current form view.

How do I call the Dynamics 365 Web API from JavaScript without a token?

`Xrm.WebApi.online` handles authentication automatically using the current user's session. You do not need to manage OAuth tokens for in-form scripts. Pass the entity logical name and record ID directly to `createRecord`, `retrieveRecord`, `updateRecord`, and `deleteRecord`.

Can I use async/await in Dynamics 365 JavaScript web resources?

Yes. Dynamics 365 runs in Chromium-based browsers (Edge and Chrome) that fully support ES2017 async/await. This is the recommended pattern for Xrm.WebApi calls — cleaner than chained `.then()` callbacks and errors are handled with standard try/catch blocks.

How do I prevent a form save in Dynamics 365 JavaScript?

In your OnSave event handler, call `executionContext.getEventArgs().preventDefault()` to cancel the save. Show a field notification first so the user knows why the save was blocked. Note: this only prevents the client-side save — it does not prevent saves from plugins or direct API calls.


Daniel Harper

Contributor, Dynamics 365 Group

This article is written by Daniel Harper for Dynamics 365 Group. Product behavior, deployment choices, and licensing can change, so confirm current Microsoft documentation before making an implementation decision.

Calculate Your Dynamics 365 Migration ROI

See how much you could save with a free, instant ROI estimate — no signup required.

Get Your Free ROI Estimate