TL;DR: The Dynamics 365 Client API centers on
formContext(retrieved fromexecutionContext) andXrm.WebApifor data operations. The olderXrm.Pagenamespace 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/awaitor Promises for all Web API calls. Never use synchronous XHR. - Consolidate multiple
retrieveRecordcalls into a singleretrieveMultipleRecordswith$selectto 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
OnLoadrather than re-querying on eachOnChange. - Keep
OnLoadhandlers 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:
- The function name in the event registration must match exactly (case-sensitive) the function name in your JavaScript file.
- The web resource must be added to the Event Library on the form before the function can be registered.
- Publish all customizations after making changes. Unpublished web resources run the old code.
- 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 GroupThis 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