JavaScript in Dynamics 365 – Xrm.WebApi, XMLHttpRequest & Practical Code Examples
A step-by-step guide for beginners and professionals on the modern way to write JavaScript in D365 — covering Xrm.WebApi, deprecated XMLHttpRequest, form utilities, and real-world code examples.
- Introduction
- What is JavaScript in Dynamics 365?
- Xrm.WebApi – The Modern Standard
- XMLHttpRequest – Now Going Deprecated
- Other Important JavaScript Utilities
- Code Example 1 – Retrieve Multiple Records
- Code Example 2 – First Record with All Field Types
- Code Example 3 – Retrieve Single Record by ID
- OData Query Options – Quick Reference
- Key Takeaways
Beginners just starting with D365 JavaScript and experienced developers wanting to modernise their codebase.
Xrm.WebApi operations, why XMLHttpRequest is deprecated, formContext, navigation, and utility helpers.
3 complete, copy-ready JavaScript examples covering multiple records, all field types, and single-record fetch.
Write clean, future-proof D365 JavaScript that won't break in modern browsers or upcoming platform updates.
Introduction
If you are working with Dynamics 365 or Power Apps Model-Driven Apps, JavaScript is one of the most important skills you need. It allows you to add custom logic, fetch data, update records, and control form behaviour — all without writing server-side code.
In this guide, we will cover everything step by step:
- ⚡What is Xrm.WebApi and why it is the right choice today
- ⚠️What is XMLHttpRequest and why Microsoft is deprecating it
- 🔧Other important JavaScript utilities every D365 developer must know
- 📋Real code examples — retrieve multiple records, read all field types, and fetch a single record by GUID
Whether you are just starting with D365 development or already have experience, this guide will help you write better, cleaner, and future-proof JavaScript.
What is JavaScript in Dynamics 365?
In Dynamics 365, JavaScript is written inside Web Resources. These are .js files that you upload to your solution and attach to form events — such as form load, field change, or record save.
JavaScript in D365 allows you to:
- 📥Read and write field values on a form
- 🔍Fetch records from Dataverse using the Web API
- 📝Create, update, or delete records programmatically
- 🔔Show messages, alerts, and confirmation dialogs
- 🔗Navigate between records and open forms
Microsoft provides a built-in global object called Xrm which gives you access to everything — form context, WebApi, navigation, and utilities. This is your starting point for all JavaScript development in D365.
⚡ Xrm.WebApi – The Modern Standard
What is Xrm.WebApi?
Xrm.WebApi is the official Microsoft-recommended JavaScript API for interacting with Dataverse data. It replaced the old XMLHttpRequest approach and is now the standard for all D365 JavaScript development.
Supported Operations
| Operation | Method | When to Use |
|---|---|---|
| Get a single record by ID | Xrm.WebApi.retrieveRecord | When you already have the record GUID |
| Get multiple records | Xrm.WebApi.retrieveMultipleRecords | When you need a list with filters |
| Create a new record | Xrm.WebApi.createRecord | Insert new data into Dataverse |
| Update an existing record | Xrm.WebApi.updateRecord | Modify fields of an existing record |
| Delete a record | Xrm.WebApi.deleteRecord | Remove a record from Dataverse |
⚠️ XMLHttpRequest – Now Going Deprecated
What is XMLHttpRequest?
XMLHttpRequest (XHR) was the old way of making HTTP calls in Dynamics 365 JavaScript. Before Xrm.WebApi existed, developers used XHR to call the Web API directly. Many older projects still use it — however, Microsoft has officially deprecated synchronous XMLHttpRequest in form scripts, and modern browsers are actively restricting its use.
What Did the Old Code Look Like?
// ❌ OLD WAY – Do NOT use this in new development
var req = new XMLHttpRequest();
req.open(
"GET",
Xrm.Page.context.getClientUrl() +
"/api/data/v9.2/accounts?$select=name,telephone1",
false // false = synchronous — this freezes the entire form
);
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.setRequestHeader("Accept", "application/json");
req.send();
var result = JSON.parse(req.responseText);
console.log(result.value[0].name);
Why Is It Deprecated?
| Problem | Explanation |
|---|---|
| 🚫 Synchronous execution | The form completely freezes while waiting for the API response |
| 🚫 Poor user experience | Users cannot interact with the form during the call |
| 🚫 Browser restrictions | Chrome, Edge, and Firefox are actively blocking synchronous XHR |
| 🚫 Microsoft warning | Official Microsoft docs now flag this as deprecated behaviour |
| ✅ Use instead | Xrm.WebApi — async, promise-based, and fully supported |
🔧 Other Important JavaScript Utilities in D365
Before jumping into code examples, here are the other commonly-used objects every D365 JavaScript developer uses on a daily basis.
1. formContext – Working with Form Fields
formContext is how you read and write values on a Dynamics 365 form. It is always obtained through the executionContext parameter passed to your function.
executionContext.getFormContext() to get the form context. The old Xrm.Page approach is also deprecated and must not be used in any new development.
function onFormLoad(executionContext) {
// Always get formContext from executionContext — never use Xrm.Page
var formContext = executionContext.getFormContext();
// ✅ Read a field value
var accountName = formContext.getAttribute("name").getValue();
console.log("Account Name: " + accountName);
// ✅ Set a field value
formContext.getAttribute("telephone1").setValue("9999999999");
// ✅ Make a field required
formContext.getAttribute("emailaddress1").setRequiredLevel("required");
// ✅ Show or hide a field control
formContext.getControl("telephone1").setVisible(false);
// ✅ Disable a field (read-only)
formContext.getControl("name").setDisabled(true);
}
2. Xrm.Navigation – Open Forms and Dialogs
Use Xrm.Navigation to open record forms, show alerts, or display confirmation dialogs to the user.
3. Xrm.Utility – Helper Functions
Use Xrm.Utility for common helper operations like showing a loading spinner, getting the logged-in user's ID, or reading the organisation URL.
// ✅ Show a loading spinner while processing
Xrm.Utility.showProgressIndicator("Please wait, loading data...");
// ✅ Hide the loading spinner when done
Xrm.Utility.closeProgressIndicator();
// ✅ Get current logged-in user ID
var userId = Xrm.Utility.getGlobalContext().getUserId();
console.log("User ID: " + userId);
// ✅ Get current organisation base URL
var orgUrl = Xrm.Utility.getGlobalContext().getClientUrl();
console.log("Org URL: " + orgUrl);
// ✅ Get current user security roles
var userRoles = Xrm.Utility.getGlobalContext().getUserRoles();
console.log("User Roles: " + userRoles);
📋 Step-by-Step Code Examples
This is the most common requirement in D365 development — fetching a list of records from Dataverse with specific fields and filter conditions. We use Xrm.WebApi.retrieveMultipleRecords with OData query options.
Build the OData query options
Use $select, $filter, $orderby, and $top to control exactly what data you get back.
Call retrieveMultipleRecords
Pass the entity logical name and your options string to the API method.
Check if records were returned
Always check result.entities.length before attempting to read any data.
Loop and read each field
Use forEach to iterate and read the value of each field by its logical name.
Handle errors
Always add an error function — never leave .then() without one. Silent failures are hard to debug.
function getMultipleAccounts() {
// Step 1 – Build the OData query options
// $select – only fetch the fields you need (never fetch all columns)
// $filter – statecode 0 = Active records only
// $orderby – sort by name A to Z
// $top – limit to 50 records to avoid performance issues
var options = "?$select=name,telephone1,emailaddress1,statecode" +
"&$filter=statecode eq 0" +
"&$orderby=name asc" +
"&$top=50";
// Step 2 – Call retrieveMultipleRecords
Xrm.WebApi.retrieveMultipleRecords("account", options).then(
function success(result) {
// Step 3 – Check if any records were returned
if (result.entities.length === 0) {
console.log("No active accounts found.");
return;
}
console.log("Total Records Found: " + result.entities.length);
console.log("===================================");
// Step 4 – Loop through each record and read field values
result.entities.forEach(function(record) {
var name = record["name"] || "Not Available";
var phone = record["telephone1"] || "Not Available";
var email = record["emailaddress1"] || "Not Available";
var status = record["statecode"] === 0 ? "Active" : "Inactive";
console.log("Account Name : " + name);
console.log("Phone : " + phone);
console.log("Email : " + email);
console.log("Status : " + status);
console.log("-----------------------------------");
});
},
function error(err) {
// Step 5 – Always handle errors
console.error("Error fetching accounts: " + err.message);
Xrm.Navigation.openAlertDialog({ text: "Error: " + err.message });
}
);
}
$select to fetch only the fields you need. Fetching all columns is one of the most common performance mistakes in D365 JavaScript — it slows form load and consumes unnecessary API capacity.
This example is essential for beginners. Every field type in Dataverse is returned differently in the API response. You need to know exactly how to read each one to avoid bugs in your code.
function getFirstAccount() {
// For lookup fields, use the _fieldname_value format in $select
var options = "?$select=name,telephone1,emailaddress1," +
"revenue,numberofemployees," + // Number / Currency
"createdon," + // Date field
"donotbulkemail," + // Boolean (Two Option)
"industrycode," + // Option Set (Choice)
"_primarycontactid_value" + // Lookup field
"&$filter=statecode eq 0" +
"&$top=1"; // Only first record
Xrm.WebApi.retrieveMultipleRecords("account", options).then(
function success(result) {
if (result.entities.length === 0) {
console.log("No record found.");
return;
}
var record = result.entities[0];
// ✅ Text Field (Single Line of Text)
var name = record["name"];
var phone = record["telephone1"];
var email = record["emailaddress1"];
console.log("Name : " + name);
console.log("Phone : " + phone);
console.log("Email : " + email);
// ✅ Number and Currency Fields
var revenue = record["revenue"];
var employees = record["numberofemployees"];
console.log("Revenue : " + revenue);
console.log("Employees : " + employees);
// ✅ Date Field – Convert to readable local format
var createdOn = record["createdon"];
var formattedDate = new Date(createdOn).toLocaleDateString("en-IN");
console.log("Created On : " + formattedDate);
// ✅ Boolean / Two Option Field
var doNotEmail = record["donotbulkemail"];
console.log("Do Not Email : " + (doNotEmail ? "Yes" : "No"));
// ✅ Option Set / Choice Field
// Raw value = integer stored in Dataverse
// FormattedValue = the label the user sees on the form
var industryCode = record["industrycode"];
var industryName = record["industrycode@OData.Community.Display.V1.FormattedValue"];
console.log("Industry (Value) : " + industryCode);
console.log("Industry (Label) : " + industryName);
// ✅ Lookup Field – Three pieces of information
// 1. The GUID of the related record
// 2. The display name shown to users
// 3. The entity logical name of the related table
var contactId = record["_primarycontactid_value"];
var contactName = record["_primarycontactid_value@OData.Community.Display.V1.FormattedValue"];
var contactType = record["_primarycontactid_value@Microsoft.Dynamics.CRM.lookuplogicalname"];
console.log("Contact ID : " + contactId);
console.log("Contact Name : " + contactName);
console.log("Contact Entity : " + contactType);
},
function error(err) {
console.error("Error: " + err.message);
}
);
}
How to Read Lookup Fields – Explained Simply
Lookup fields return three separate pieces of information via OData annotations. Here is what each one gives you:
| Annotation | What It Returns | Example Value |
|---|---|---|
_primarycontactid_value | The GUID of the related record | xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |
@OData.Community.Display.V1.FormattedValue | The display name shown to users | John Smith |
@Microsoft.Dynamics.CRM.lookuplogicalname | The logical name of the related entity | contact |
industrycode) and the formatted label (@OData.Community.Display.V1.FormattedValue). Use the raw integer for comparisons in your logic and the label when displaying text to the user.
When you already know the exact GUID of a record — for example from a lookup field on the current form — use retrieveRecord instead of retrieveMultipleRecords. It is faster, simpler, and more direct.
function getSingleAccount(executionContext) {
var formContext = executionContext.getFormContext();
// Step 1 – Read the lookup field from the form
var lookupValue = formContext.getAttribute("parentaccountid").getValue();
// Step 2 – Check if a value is selected in the lookup
if (lookupValue === null || lookupValue.length === 0) {
console.log("No parent account selected on the form.");
return;
}
// Step 3 – Lookup fields always return an array – use index [0]
// Also remove curly braces from the GUID if present
var accountId = lookupValue[0].id
.replace("{", "")
.replace("}", "");
// Step 4 – Define the fields to fetch
var options = "?$select=name,telephone1,emailaddress1,_primarycontactid_value";
// Step 5 – Call retrieveRecord with entity name, GUID, and options
Xrm.WebApi.retrieveRecord("account", accountId, options).then(
function success(record) {
var name = record["name"];
var phone = record["telephone1"];
var email = record["emailaddress1"];
var contactName = record["_primarycontactid_value@OData.Community.Display.V1.FormattedValue"];
var contactId = record["_primarycontactid_value"];
console.log("Account Name : " + name);
console.log("Phone : " + phone);
console.log("Email : " + email);
console.log("Contact Name : " + contactName);
console.log("Contact ID : " + contactId);
},
function error(err) {
console.error("Error retrieving account: " + err.message);
}
);
}
formContext are always returned as an array — even when only a single record is selected. Always use lookupValue[0] to access the selected record's ID, name, and entity type.
OData Query Options – Quick Reference
These are the query options you will use most often inside your Xrm.WebApi calls. Understanding them is essential for writing efficient D365 JavaScript.
| Option | Purpose | Example |
|---|---|---|
$select | Fetch only specific fields — always use this | $select=name,telephone1 |
$filter | Filter records by a condition | $filter=statecode eq 0 |
$orderby | Sort results ascending or descending | $orderby=name asc |
$top | Limit the number of records returned | $top=50 |
$expand | Fetch fields from a related entity in the same call | $expand=primarycontactid($select=fullname) |
Active Records
$filter=statecode eq 0
Inactive Records
$filter=statecode eq 1
Filter by Text
$filter=name eq 'Contoso'
Filter by Lookup
$filter=_ownerid_value eq <GUID>
Key Takeaways
- →Always use Xrm.WebApi for all data operations — it is the official Microsoft standard for modern D365 development.
- →XMLHttpRequest is deprecated — avoid it completely in new development and plan to refactor it in older code.
- →Always use
$selectto limit the fields you fetch. Never retrieve all columns — it is the most common performance mistake in D365 JavaScript. - →Use
$filter,$orderby, and$topto keep your queries efficient and results controlled. - →For lookup fields, use the
_fieldname_valueformat in$selectand read three OData annotations to get the ID, display name, and entity type. - →For option set fields, always read both the raw integer value and the
FormattedValueannotation to get the readable label. - →Always use
executionContext.getFormContext()— never use the deprecatedXrm.Pageobject in any new development. - →Always add an error handler in every WebApi call. Never leave
.then()without an error function — silent failures are very hard to debug. - →Lookup field values from
formContextare always returned as an array — always uselookupValue[0]to access the selected record.
Xrm.WebApi and formContext, everything else starts to fall into place. Start small, test in the browser console, and build step by step. Every certified D365 developer started exactly where you are right now. Keep experimenting, keep building — and enjoy the journey. 🚀
No comments:
Post a Comment