4.3 Client Scripting Against the Dataverse Web API; Command Bar & Buttons with Power Fx and JavaScript

Key Takeaways

  • Xrm.WebApi (createRecord, retrieveMultipleRecords, execute, etc.) always hits the server and reflects only saved data, unlike formContext which reflects unsaved, in-memory form state.
  • retrieveMultipleRecords() accepts standard OData query options ($select, $filter, $expand, $orderby, $top) as a single query string.
  • The command designer's Run JavaScript action mirrors form-event registration (library + namespaced function); Run Power Fx stores an inline formula instead, avoiding a separate web resource.
  • Visibility and enable rules can be authored as Power Fx expressions in the designer or as classic <EnableRule>/<DisplayRule> ribbon XML for scenarios the designer doesn't cover.
  • Command bar customizations are scoped independently to a table's main grid, a subgrid, or a form's command bar — a change at one scope does not cascade to the others.
Last updated: July 2026

Two Extend-the-UX capabilities pair naturally: calling the Dataverse Web API from inside client scripts, and configuring the buttons that trigger scripts — or increasingly, formulas — in the modern command bar.

Xrm.WebApi: Client Scripting Against the Dataverse Web API

Xrm.WebApi (used as Xrm.WebApi.online when you need to be explicit about the connected, non-offline case) exposes the same CRUD-plus-actions surface as the Dataverse Web API, but from inside form or ribbon JavaScript: createRecord(), updateRecord(), deleteRecord(), retrieveRecord(), retrieveMultipleRecords(), and execute()/executeMultiple() for custom actions, functions, and custom APIs. Every method returns a promise, so modern code typically uses async/await:

async function loadRelatedContacts(formContext) {
    const accountId = formContext.data.entity.getId().replace(/[{}]/g, "");
    try {
        const result = await Xrm.WebApi.retrieveMultipleRecords(
            "contact",
            `?$select=fullname,emailaddress1&$filter=_parentcustomerid_value eq ${accountId}&$top=5`
        );
        return result.entities;
    } catch (error) {
        console.error(error.message);
    }
}

retrieveMultipleRecords() takes OData query options — $select, $filter, $expand, $orderby, $top — as a single string, mirroring the raw Web API syntax the platform APIs domain covers in more depth. Custom APIs and actions are invoked through execute() with a request object that implements a getMetadata() method describing its operation name, parameter types, and whether it binds to an entity.

The essential distinction PL-400 tests here is network round trip versus in-memory state: Xrm.WebApi calls always hit the server and reflect only what has already been saved to Dataverse, while formContext.getAttribute().getValue() reads whatever is currently in the browser, including unsaved edits the user hasn't committed yet. A script that needs the user's current, unsaved input must read it from formContext; a script that needs related records from other tables must go through Xrm.WebApi.

The Modern Command Bar and the Command Designer

Microsoft has moved command-bar customization from hand-edited ribbon XML toward the command designer, a low-code, per-table or per-app editor that produces a JSON-based command schema. For most day-to-day button customizations, this replaces manually authoring RibbonDiffXml; the classic ribbon workbench approach still exists and remains necessary for advanced scenarios — such as certain split-button or flyout structures — that the designer doesn't yet cover.

In the command designer, each command (button) is bound to an action, and PL-400 expects you to know the two script-relevant options:

  • Run JavaScript — behaves like form event registration: pick a library web resource, a namespaced function, and optionally map parameters (including the primary control, selected record references, or static values) into the function's arguments.
  • Run Power Fx — write the button's logic directly as a Power Fx formula, the same language used in canvas apps, without deploying a separate JavaScript web resource. Common formulas call Notify() to show a message, Navigate() to open another screen or page, Set() to manage variables, or Patch() to write data directly to Dataverse.

Visibility and Enable Rules

Whether a command shows at all, and whether it's clickable, is controlled by visibility rules and enable rules. The command designer supports Power Fx boolean expressions evaluated against the ribbon's implicit context — for example, an expression referencing whether a record is selected — for straightforward show/hide logic authored without leaving the designer. Scenarios that predate the designer, or that need logic the expression language doesn't support, fall back to classic <EnableRule> and <DisplayRule> definitions in ribbon XML, which reference a custom JavaScript function returning a boolean.

Scope: Grid, Subgrid, and Form

Command bar customizations apply at different scopes — a table's main grid, a subgrid embedded on a form, and a form's own command bar — and each scope is customized independently, whether through the designer or classic ribbon XML with an explicit ribbon location. A common exam pattern presents a requirement ("hide this button only on the account subgrid inside opportunity forms, not on the main account grid") and expects you to recognize that the customization must target the subgrid scope specifically, not the table's grid-level command bar, because a scope-level change does not automatically cascade to every place the table's records are displayed.

Error Handling and Related-Record Retrieval

Because Xrm.WebApi calls are asynchronous network requests, production-quality scripts wrap them in try/catch around the await, or attach a .catch() handler to the returned promise, rather than letting a failed request silently do nothing. A failed request typically resolves to a rejected promise carrying an error.message describing the Dataverse error, which should surface to the user through a form notification rather than only a browser console log the user will never see.

retrieveMultipleRecords() and retrieveRecord() also support $expand, which pulls related records in the same round trip instead of issuing a second call — for example, expanding an account's primary contact alongside the account itself avoids a follow-up retrieveRecord() call purely to read the contact's email address. Favoring $expand over sequential calls matters for the "Optimize and troubleshoot apps" domain's performance concerns, since each additional round trip adds latency the user directly experiences while a form or command is running.

Test Your Knowledge

A command button must run without deploying a separate JavaScript web resource. Which command-designer action lets the developer write the button's logic as an inline formula, similar to a canvas app's formula bar?

A
B
C
D
Test Your Knowledge

Which Client API call retrieves related account records directly from the server, independent of any unsaved changes currently sitting in the form's fields?

A
B
C
D