Sam's Dynamics, Power Platform & AI Blog

Monday, 14 September 2026

Calling the Dataverse Web API from Dynamics 365 Form Scripts

Everything so far in this series has stayed on the form: fields, events, lookups, subgrids. This post steps off the form and into Dataverse directly, using the Web API to read and write records the form doesn't already have loaded — the foundation you need before the next post, where this starts connecting to Copilot Studio agents.

Why Call the Web API from a Form Script

A few common reasons: pulling in data that isn't on the form (like a related account's credit limit), creating a related record without navigating the user away, running a real-time check against other records before save, or calling a custom Dataverse action. All of it goes through Xrm.WebApi, and every method returns a Promise.

The Basics: Xrm.WebApi

// Read one record
Xrm.WebApi.retrieveRecord("account", accountId, "?$select=name,creditlimit");

// Read multiple records
Xrm.WebApi.retrieveMultipleRecords("contact", "?$select=fullname&$filter=parentcustomerid_value eq " + accountId);

// Create a record
Xrm.WebApi.createRecord("task", {
    subject: "Follow up",
        "regardingobjectid_account@odata.bind": "/accounts(" + accountId + ")"
        });
        
        // Update a record
        Xrm.WebApi.updateRecord("account", accountId, { creditlimit: 50000 });

Note the entity set names are plural and lowercase (accounts, contacts), but retrieveRecord/createRecord/updateRecord take the singular logical name (account) — it's a common source of confusion.

Reading a Related Record

Here's a real example: when a contact form loads, pull the parent account's credit limit and show it in a notification:

async function onLoad(executionContext) {
    var formContext = executionContext.getFormContext();
        var accountValue = formContext.getAttribute("parentcustomerid").getValue();
            if (!accountValue) return;
            
                var accountId = accountValue[0].id.replace("{", "").replace("}", "");
                
                    try {
                            var result = await Xrm.WebApi.retrieveRecord("account", accountId, "?$select=creditlimit");
                                    formContext.ui.setFormNotification(
                                                "Account credit limit: " + (result.creditlimit || 0),
                                                            "INFO",
                                                                        "credit_limit_info"
                                                                                );
                                                                                    } catch (error) {
                                                                                            console.error("Failed to retrieve account credit limit", error);
                                                                                                }
                                                                                                }

Creating a Related Record

async function logFollowUpTask(executionContext) {
    var formContext = executionContext.getFormContext();
        var contactId = formContext.data.entity.getId().replace("{", "").replace("}", "");
        
            var task = {
                    subject: "Follow up on discount request",
                            "regardingobjectid_contact@odata.bind": "/contacts(" + contactId + ")"
                                };
                                
                                    try {
                                            await Xrm.WebApi.createRecord("task", task);
                                                } catch (error) {
                                                        console.error("Failed to create follow-up task", error);
                                                            }
                                                            }

A Practical Example: Real-Time Duplicate Check on Save

Combining an async Web API call with OnSave, including blocking the save until the check completes:

formContext.data.entity.addOnSave(onSaveCheckDuplicate);

async function onSaveCheckDuplicate(executionContext) {
    var eventArgs = executionContext.getEventArgs();
        var formContext = executionContext.getFormContext();
            var email = formContext.getAttribute("emailaddress1").getValue();
            
                if (!email) return;
                
                    eventArgs.preventDefault(); // pause the save while we check
                    
                        try {
                                var query = "?$select=contactid&$filter=emailaddress1 eq '" + email + "'";
                                        var result = await Xrm.WebApi.retrieveMultipleRecords("contact", query);
                                        
                                                var isDuplicate = result.entities.some(function (c) {
                                                            return c.contactid !== formContext.data.entity.getId().replace("{", "").replace("}", "");
                                                                    });
                                                                    
                                                                            if (isDuplicate) {
                                                                                        formContext.ui.setFormNotification("A contact with this email already exists.", "ERROR", "dup_check");
                                                                                                } else {
                                                                                                            formContext.data.entity.save(); // safe to save now
                                                                                                                    }
                                                                                                                        } catch (error) {
                                                                                                                                console.error("Duplicate check failed", error);
                                                                                                                                        formContext.data.entity.save(); // don't block save on a broken check
                                                                                                                                            }
                                                                                                                                            }

Common Pitfalls

  • Forgetting await. Without it, your code moves on before the API call resolves, and you end up reading undefined data or saving before a check completes.
  • Not handling rejected promises. A network error or a security-role denial throws — always wrap calls in try/catch.
  • Wrong entity set name. retrieveMultipleRecords needs the plural set name; retrieveRecord/createRecord/updateRecord need the singular logical name.
  • Too many calls per save. Each Web API call is a real HTTP request; batch or cache where you can instead of calling it inside a loop.

What's Next

Last post in this series: wiring a Copilot Studio agent action into a JavaScript-driven ribbon button — where form scripting and agents meet.

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...