ServiceNow Client script examples
onChange, onLoad and onSubmit client script patterns.
Client scripts run in the browser while a form is open: onLoad for defaults after render, onChange when a named field changes, onSubmit to validate and return false to stop the save. Every onChange starts with the isLoading guard, or it fires for every field during load. Keep them light with asynchronous calls, and never rely on them for security.
- onChange: react to a field
- onLoad: default a field
- onSubmit: validate
Client Scripts react to form events in the browser. Keep them light and never trust them for security.
onChange: react to a field
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading || newValue === '') return;
g_form.setValue('urgency', newValue);
}onLoad: default a field
function onLoad() {
if (g_form.getValue('category') === '')
g_form.setValue('category', 'inquiry');
}onSubmit: validate
function onSubmit() {
if (g_form.getValue('short_description') === '') {
g_form.addErrorMessage('Description is required');
return false;
}
}The four types, and when each one runs
A client script is JavaScript that runs in the user's browser while a form is open. There are four types and choosing the wrong one is the most common reason a script appears not to work.
onLoad runs once, after the form has rendered. Use it for defaults and for setting up the initial state.
onChange runs when one named field changes. You choose the field when you create the script, and the function receives the old and new values.
onSubmit runs when the user saves, and returning false stops the save. This is where form level validation belongs.
onCellEdit runs when a value is edited inline in a list rather than on a form, which is a path people forget entirely until somebody edits a hundred records from a list and none of the form logic fires.
Where they live: the sys_script_client table, scoped to a table and optionally to a view,
with an active flag and an order. Two scripts on the same field both run, in order, and each sees what the
previous one did.
Order matters more than people expect. Scripts on the same table and field run in order value sequence, and each one sees the form as the previous one left it. So a script that reads a field another script is about to set produces different results depending on which happened to be created first. When two scripts touch the same field, set their order deliberately rather than leaving both at the default, and write down why.
The isLoading guard, and why every onChange starts with it
Every onChange example starts with the same two lines and it is worth knowing what they prevent.
if (isLoading || newValue === '') return;
When a form loads, the platform populates every field, and each of those counts as a change. So without the guard, an onChange script runs during load, for every record, doing work that was only ever meant to happen when a user typed something. The visible symptoms are a form that flashes values, defaults that overwrite saved data, and messages appearing when nothing has happened.
The newValue === '' half handles the case where a field is cleared, where acting on an empty
value usually makes no sense.
A related trap: setting a field inside its own onChange script triggers the script again. The guard does not stop that, because it is a real change. Either set a different field, or check whether the value is already what you are about to set it to.
Build one, and watch it misbehave first
Twenty minutes on a developer instance, and the guard stops being boilerplate.
- On the incident form, create an onChange script on
categorythat setsurgency, and deliberately leave out the isLoading guard. - Open an existing incident. The urgency changes on load, on a record nobody touched.
- Add the guard. Reload. The value now stays as saved, and changing the category still works .
- Now add an onSubmit script that rejects a short description under ten characters, and try to save.
- Open the same record through a list and edit the category inline. Neither script fires, because inline editing is onCellEdit.
- Open the browser console and watch for errors. A script that throws stops silently and everything after it in the same script does not run.
Step five is the one worth remembering. Validation that exists only on the form is not validation. See business rules.
Keeping them light
Client scripts run while the user waits, so anything slow in one is felt directly as a form that hangs. Two rules cover most of it.
Do not query the server synchronously. A synchronous GlideRecord or GlideAjax call from a client script freezes the browser until it returns. Use the asynchronous form with a callback. The code is slightly more awkward and the form stays responsive.
Prefer configuration to code. A great many client scripts reimplement something the platform already does declaratively: making a field mandatory, hiding a section, setting a default. UI policies do all three, run faster, and are visible to anybody reading the configuration rather than buried in JavaScript. If a client script only shows, hides, or makes fields mandatory, it should be a UI policy.
Scope matters too. A script with no view specified runs on every view of that table, including mobile and including views built for a different audience entirely.
One more habit that pays off in support: keep each script to one job. A single onChange script that sets three fields, shows a message and calls the server is very hard to debug when one part of it stops working, because the whole thing fails together. Three small scripts fail independently and each one is obvious from its name.
Never trust them for security
This is the point the introduction makes and it deserves expanding, because it is the mistake with the worst consequences. A client script runs in the browser, which means the user can see it, skip it, or bypass it entirely by using an API. A record submitted through the REST interface never runs any client script at all.
So a required field enforced only by an onSubmit script is not required, and a value protected only by a client script is not protected. Anything that must hold has to be enforced on the server, in a business rule, a data policy, or an access control. The client script is there to help the person filling in the form, which is a real and useful job, and it is not a control.
Version note: the four types and the guard above are stable across releases. What has changed is the surrounding platform: Service Portal and the mobile and workspace interfaces do not run classic client scripts the same way, so a script that works on the platform form may do nothing in a portal. Check where your users actually are before assuming a script reaches them. See client snippets and GlideRecord examples.
The practical consequence for design is that the same rule usually has to exist twice: once on the server where it is enforced, and once on the client where it is helpful. That feels like duplication and it is not avoidable, because the two are doing different jobs. What you can avoid is the two disagreeing, so write the server rule first and make the client script match it rather than the other way round.
Common pitfalls
- Missing isLoading guard. The script runs on every form load.
- Synchronous server calls. The browser freezes and users report the form is broken.
- Validation only on the client. The API path ignores it.
- A client script doing a UI policy's job. Slower and harder to find.
- No view specified. It runs everywhere, including places you never tested. See flow examples for logic that belongs off the form entirely.
Where this goes next
The four types take an afternoon, and knowing which logic belongs on the client, which belongs in a UI policy and which has to be on the server is the judgement the course builds.