A form script doesn't run continuously in the background — it wakes up in response to specific moments in the life of a form, called events. Dynamics 365 CE exposes several of these, but nearly every script you'll write hooks into one of three: OnLoad, OnChange, and OnSave. Knowing which one to use for a given piece of logic is one of the most practical skills in form scripting.
- OnLoad fires once, right after the form finishes loading
- OnChange fires every time one specific field's value changes
- OnSave fires just before the record is saved — and can cancel the save
- All three receive an execution context as their first parameter
Motion-sensor lights around a house
Picture motion-sensor lights around a house. One switches on the moment you first walk into a room, setting the initial state. A second triggers only when one specific object — like a cabinet door — is touched. A third, at the front door, can still stop you and make you double back before you're allowed to leave. Dynamics 365 CE's three core form events map onto exactly this pattern.
| Event | Fires when | Typical use |
|---|---|---|
| OnLoad | The form finishes loading, once, when it first opens | Set initial visibility, default values, initial field state |
| OnChange | A specific field's value changes | React to user input on that one field, e.g. show a related field |
| OnSave | Just before the record is saved | Final validation; can cancel the save entirely |
OnLoad — the walk-in light
An OnLoad handler runs once, right after the form has finished rendering and loading its data, before the user has done anything at all. It's the natural place to set up the form's starting state — hiding a section that should only appear under certain conditions, applying a default, or disabling a field based on the record's current status.
Because it only fires once per form load, it's not the place to react to something the user is actively typing.
OnChange — the cabinet sensor
An OnChange handler is tied to one specific field, and fires every time that field's value changes — typed, picked from a lookup, or set programmatically. This is where most "reactive" form behavior lives:
- Showing a "Cancellation Reason" field the moment Status changes to Cancelled
- Recalculating a total when a quantity changes
- Warning the user the instant they pick an inactive account
function onPriorityChange(executionContext) {
var formContext = executionContext.getFormContext();
var priority = formContext.getAttribute("prioritycode").getValue();
if (priority === 1) {
formContext.getAttribute("duedate").setValue(new Date());
}
}
OnSave — the front-door check
An OnSave handler fires just before the record is committed to the database, no matter which field triggered the save. It's the last checkpoint before data leaves the form — the right place for validation that needs to consider the record as a whole, not one field in isolation. For example, requiring a "Retirement Date" whenever Status is set to Retired, regardless of which field the user changed last.
Critically, an OnSave handler can actually stop the save from happening, by calling executionContext.getEventArgs().preventDefault() — the same way that front-door sensor can make you turn back before you're allowed to leave.
function onSaveValidate(executionContext) {
var formContext = executionContext.getFormContext();
var status = formContext.getAttribute("statuscode").getValue();
var reason = formContext.getAttribute("cancellationreason").getValue();
if (status === 2 && !reason) {
executionContext.getEventArgs().preventDefault();
formContext.ui.setFormNotification("Cancellation reason is required.", "ERROR", "cancelcheck");
}
}
Execution context & event args syntax reference
Every handler you register receives an execution context as its first parameter — Dynamics 365 CE passes it in automatically, you just name the parameter in your function. A few of its methods come up constantly:
| Method | What it does |
|---|---|
executionContext.getFormContext() | Returns the form context covered in the previous chapter |
executionContext.getEventSource() | On OnChange, returns the specific attribute that triggered the event — useful when one function is registered on several fields |
executionContext.getSharedVariable(key) / .setSharedVariable(key, value) | Passes a value between multiple handlers registered on the same event, in the order they run |
executionContext.getEventArgs() | Returns event-specific arguments — on OnSave, this is where the save-blocking methods below live |
On OnSave specifically, getEventArgs() returns a save event object with its own methods:
| Method | What it does |
|---|---|
.preventDefault() | Cancels the save entirely |
.isDefaultPrevented() | True if some earlier handler already called preventDefault() this round |
.getSaveMode() | Returns a number identifying what triggered the save — common values are 1 (Save), 2 (Save & Close), 59 (Save & New), and 70 (autosave) |
That last one matters in practice: you might want strict validation on a manual Save, but skip it on an autosave (mode 70) so the user isn't interrupted while just sitting on the form.
preventDefault(). The execution context passed into every handler is where all of this — the form context, the event source, and (on OnSave) the save-blocking methods — comes from.