4.2 Event Handler Registration & Navigation to Custom Pages via the Client API

Key Takeaways

  • OnLoad fires once at load, OnChange fires per attribute value change, and OnSave can inspect the save mode and cancel the save with preventDefault().
  • A handler must have its library attached to the form, be referenced by a namespaced function name, and have the execution-context checkbox selected, or it silently fails or misbehaves.
  • Namespacing function names (e.g., Contoso.Forms.Account.onLoad) avoids collisions when multiple libraries load on the same form.
  • Xrm.Navigation.navigateTo() opens custom pages inline or as a dialog and can pass parameters, including the current record's ID.
  • Structured dialog helpers (openAlertDialog, openConfirmDialog, openErrorDialog) should replace native browser alert()/confirm() calls.
Last updated: July 2026

Client API code only runs when something triggers it. This section covers how form events are registered so your JavaScript actually executes at the right moment, and how the Client API navigates users to custom pages — the canvas-app-style surfaces that can be embedded inside a model-driven app.

Which Form Events Fire, and When

EventFires whenTypical use
OnLoadOnce, after the form finishes loading but before it renders to the userSet defaults, conditionally hide fields, apply initial notifications
OnChangeAn attribute's value changes, either by the user or by setValue() followed by fireOnChange()Cascading logic — e.g., filtering a lookup based on another field
OnSaveSave is initiated, whether by the ribbon, autosave, or formContext.data.save()Final validation; can block the save entirely
TabStateChangeA tab is expanded or collapsedLazy-load content, adjust layout
GridLoad / OnRecordSelectA subgrid loads or a row is selectedSubgrid-specific customization

Multiple handlers can be registered on the same event, and they execute in the order listed in the form's Event Handlers configuration — but relying on implicit ordering across unrelated libraries is fragile, and PL-400 scenario questions often hinge on recognizing that a handler didn't run at all rather than ran in the wrong order.

Registering a Handler

Registration happens in the form designer's Event Handlers pane, not in code. Three pieces must line up:

  1. Library — the JavaScript web resource must first be added to the form's library list; a function cannot be referenced from a library that isn't attached to the form.
  2. Function — specify a fully namespaced function, such as Contoso.Forms.Account.onLoad, rather than a bare global function name, to avoid collisions when multiple ISV or customization libraries load on the same form.
  3. Pass execution context as first parameter — this checkbox must be selected, or the handler receives no arguments at all, making executionContext (and therefore formContext) undefined inside the function. This single unchecked box is one of the most common causes of "my script doesn't work" bugs in real projects and on the exam.
var Contoso = Contoso || {};
Contoso.Forms = Contoso.Forms || {};
Contoso.Forms.Account = Contoso.Forms.Account || {};

Contoso.Forms.Account.onLoad = function (executionContext) {
    var formContext = executionContext.getFormContext();
    // initialization logic
};

OnSave: Reading Save Mode and Cancelling a Save

The OnSave handler receives the same executionContext, but you additionally call executionContext.getEventArgs() to get a SaveEventArgs object. getSaveMode() returns a numeric code identifying why the save fired — a plain Save, Save and Close, Save and New, Deactivate, or an autosave — which lets validation logic behave differently for interactive saves versus background autosaves. Calling eventArgs.preventDefault() cancels the save entirely, which is the standard pattern for client-side validation that must block persistence, such as rejecting an out-of-range value before it reaches the server.

Navigating to Custom Pages

Custom pages are canvas-app-style pages that live inside a model-driven app rather than as a standalone canvas app. They can be reached through the app's site map, opened from a command, or launched programmatically with Xrm.Navigation.navigateTo().

var pageInput = {
    pageType: "custom",
    name: "cr123_customreviewpage_1a2b3",
    entityName: "account",
    recordId: formContext.data.entity.getId()
};
var navigationOptions = {
    target: 2,               // open as a dialog
    position: 1,              // centered
    width: { value: 60, unit: "%" },
    height: { value: 80, unit: "%" }
};

Xrm.Navigation.navigateTo(pageInput, navigationOptions).then(
    function success() { /* dialog closed */ },
    function error(e) { console.error(e); }
);

pageInput identifies the custom page and can pass parameters (including the calling record's ID) into it; navigationOptions.target chooses whether the page opens inline in place of the calling content or as a dialog, with position and width/height controlling dialog placement. navigateTo() returns a promise that resolves when the dialog closes, which is useful for refreshing the calling form after the custom page finishes its work.

Xrm.Navigation also exposes openForm() (open a different record's form), openAlertDialog(), openConfirmDialog(), openErrorDialog(), and openUrl(). These structured dialog helpers should be used instead of native browser alert()/confirm(), which are inconsistent with the app's UI and can be blocked by the browser. Knowing which navigation method matches a given requirement — opening an existing record's form versus a custom page versus a plain URL — is a frequent scenario pattern on the exam.

Disabling Versus Removing a Handler

The Event Handlers pane also has an Enabled checkbox per registered handler, independent of the handler's presence in the list. Unchecking it stops the handler from firing without deleting the registration, which matters when troubleshooting: a disabled handler is invisible at runtime but still shows up if a colleague later inspects the form's customizations, which can be confusing if the checkbox state isn't documented. Handlers can also be removed entirely with removeOnChange() for attribute-level OnChange registrations made dynamically from code — for example, a script that conditionally wires up a temporary OnChange handler during a wizard-like flow and tears it down again once the flow completes, rather than leaving a stale listener attached for the rest of the session.

Static Parameters and Passing Data into a Handler

Beyond the execution context, the Event Handlers pane lets a developer supply comma-separated static parameters that get appended after executionContext when the function is invoked, which is useful for reusing one generic library function across several fields or forms with different configuration per registration (for example, a single Contoso.Forms.Shared.validateRange function reused on both a credit-limit field and a discount-percentage field, each registered with different min/max static parameters). This pattern avoids duplicating near-identical functions across web resources purely to hardcode different thresholds, and PL-400 scenario questions occasionally test whether a candidate recognizes reusable, parameterized handlers as the more maintainable design over one-off copies.

Test Your Knowledge

A form library's OnLoad handler is registered in the form designer's Event Handlers pane, but formContext inside the function is undefined at runtime. What is the most likely cause?

A
B
C
D
Test Your Knowledge

Which Xrm.Navigation call should a developer use to open a custom page as a centered dialog and pass the current record's ID into it?

A
B
C
D