Client Scripts
Client Script Overview
Client Scripts are JavaScript programs that execute in the user's browser — not on the ServiceNow server. They allow administrators and developers to dynamically control form behavior, validate user input, and interact with the UI in real time.
Runs In Browser
Client Scripts execute client-side (in the browser). They have no direct access to server-side APIs like GlideRecord. For server data, use GlideAjax.
Storage Table
All Client Scripts are stored in the sys_client_script table. Navigate to: System Definition > Client Scripts.
Scoped to Table
Each Client Script is associated with a specific table (e.g., incident). They fire only when that table's form or list is active.
4 Script Types
There are exactly four Client Script types: onLoad, onChange, onSubmit, and onCellEdit. Each fires at a different user interaction point.
Script Types — CRITICAL EXAM TOPIC
Knowing exactly when each script type fires and what parameters it receives is essential for the CSA exam. This is one of the most heavily tested areas.
onLoad
Use for: Setting initial field states, pre-populating values, hiding fields based on user role, or configuring the form layout on first render.
Parameters: None.
// onLoad example: hide resolution fields when opening a new incident
function onLoad() {
var state = g_form.getValue('state');
// If state is not Resolved, hide resolution code
if (state != '6') {
g_form.setVisible('close_code', false);
g_form.setVisible('close_notes', false);
}
}
onChange
Use for: Reacting to field changes — show/hide related fields, update dependent fields, validate the new value.
Parameters:
(tableName, oldValue, newValue, isLoading, isTemplate)
// onChange example: show resolution fields when state changes to Resolved
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
// ALWAYS include this guard — prevents firing on form load
if (isLoading) return;
if (isTemplate) return;
if (newValue == '6') { // 6 = Resolved
g_form.setVisible('close_code', true);
g_form.setMandatory('close_code', true);
g_form.setVisible('close_notes', true);
g_form.setMandatory('close_notes', true);
} else {
g_form.setVisible('close_code', false);
g_form.setMandatory('close_code', false);
g_form.setVisible('close_notes', false);
g_form.setMandatory('close_notes', false);
}
}
onSubmit
Use for: Final validation before save. Return false to cancel the submit and keep the user on the form.
Parameters: None explicitly (but you can call
g_form.getValue() inside).
// onSubmit example: prevent save if priority is Critical but no manager approval
function onSubmit() {
var priority = g_form.getValue('priority');
var approvalState = g_form.getValue('approval');
if (priority == '1' && approvalState != 'approved') {
g_form.addErrorMessage('Critical priority incidents require manager approval before saving.');
return false; // Cancel the save — user stays on the form
}
// Returning true (or nothing) allows the save to proceed
return true;
}
onCellEdit
Use for: Validating or reacting to changes made directly in a list without opening the full form record.
Key distinction: onCellEdit does NOT fire on a form — it is exclusively for list editing.
Full Comparison Table
| Type | When It Fires | Where | Return false effect | Parameters |
|---|---|---|---|---|
| onLoad | Form is loaded/displayed | Form only | No effect | None |
| onChange | Specified field value changes | Form only | No effect | control, oldValue, newValue, isLoading, isTemplate |
| onSubmit | Save/Update/Submit clicked | Form only | Cancels the save | None |
| onCellEdit | Cell edited in list view | List only | Cancels the edit | sysIDs, table, oldValues, newValue, callback |
g_form API — Critical, Memorize
The g_form object is the primary API for interacting with form fields from a Client Script. It is available in all form-based script types (onLoad, onChange, onSubmit). You must know these methods cold for the exam.
Getting and Setting Values
g_form.getValue('field')
Returns the current value of a field. For reference fields, returns the sys_id of the referenced record (not the display value).
g_form.setValue('field', 'value')
Sets the value of a field. For reference fields, pass the sys_id. This triggers onChange listeners for the field being set.
g_form.getDisplayValue('field')
Returns the display value of a field (what the user sees). Useful for reference fields where getValue returns a sys_id.
g_form.clearValue('field')
Clears the current value of a field, setting it to empty/null. Equivalent to calling setValue('field', '').
Field Visibility and State
g_form.setVisible('field', true/false)
Shows (true) or hides (false) a field on the form. Hidden fields still submit their current value.
g_form.setMandatory('field', true/false)
Makes a field required (true) or optional (false). Mandatory fields show a red asterisk and block save if empty.
g_form.setReadOnly('field', true/false)
Makes a field read-only (true) or editable (false). Read-only fields display their value but cannot be edited by the user.
g_form.setDisplay('field', true/false)
Similar to setVisible, but also removes the field's space from the layout when hidden. More aggressive than setVisible.
Messages and Notifications
g_form.addInfoMessage('msg')
Displays a blue informational banner at the top of the form. Useful for guidance or context messages.
g_form.addErrorMessage('msg')
Displays a red error banner at the top of the form. Commonly used in onSubmit to explain why a save was cancelled.
g_form.clearMessages()
Removes all info and error banners from the form. Call this before adding new messages to avoid duplicates stacking up.
g_form.showFieldMsg('field', 'msg', 'type')
Displays a message directly below a specific field. Type can be 'info' or 'error'. More targeted than form-level banners.
DOM and Label Access
g_form.getControl('field')
Returns the DOM element for the specified field. Use for advanced UI manipulation beyond the standard g_form API.
g_form.getLabel('field')
Returns the label text of a field as displayed on the form. Useful for dynamic messages that reference field names.
g_form.getSections()
Returns an array of all form sections. Use with g_form.setSectionDisplay() to show or hide entire form sections.
g_form.isNewRecord()
Returns true if the current form is a new (unsaved) record, false if editing an existing record.
Complete g_form Reference
| Method | Purpose | Returns |
|---|---|---|
getValue('field') | Get field value (sys_id for references) | String |
getDisplayValue('field') | Get display value (label for references) | String |
setValue('field', val) | Set field value | void |
clearValue('field') | Empty a field | void |
setVisible('field', bool) | Show/hide a field (preserves layout space) | void |
setDisplay('field', bool) | Show/hide a field (removes layout space) | void |
setMandatory('field', bool) | Make field required/optional | void |
setReadOnly('field', bool) | Make field read-only/editable | void |
addInfoMessage('msg') | Show blue info banner | void |
addErrorMessage('msg') | Show red error banner | void |
clearMessages() | Remove all form banners | void |
showFieldMsg('field', 'msg', 'type') | Show message under a field | void |
getControl('field') | Get the DOM element for a field | DOM Element |
getLabel('field') | Get the label text of a field | String |
isNewRecord() | Check if this is a new record | Boolean |
getSysId() | Get the sys_id of the current record | String |
getTableName() | Get the table name of the current form | String |
g_form.setMandatory() enforces mandatory only in the browser UI. It can be bypassed by sending a REST API request directly. For true enforcement that cannot be bypassed, use a Business Rule or ACL on the server side.
g_user API
The g_user object provides information about the currently logged-in user on the client side. It is available in all Client Script types and also in UI Policies.
g_user.hasRole() uses cached role data loaded when the session started. It is NOT a real-time security check — it simply reads what roles the browser session believes the user has. Never use g_user for security enforcement; use server-side checks (ACLs, Business Rules with gs.hasRole()) instead.
g_user Properties and Methods
| Property / Method | Description | Example Value |
|---|---|---|
g_user.userID |
The sys_id of the currently logged-in user | "a8f98bb0eb32010045e1a5115206fe3a" |
g_user.userName |
The login/username (user_name field) | "john.doe" |
g_user.firstName |
The user's first name | "John" |
g_user.lastName |
The user's last name | "Doe" |
g_user.getFullName() |
Returns the user's full display name | "John Doe" |
g_user.hasRole('role') |
Returns true if user has the specified role (cached — not for security) | g_user.hasRole('itil') → true/false |
g_user.hasRoleExactly('role') |
Returns true only if user has the exact role — does not include inherited roles (e.g., admin) | g_user.hasRoleExactly('itil') |
g_user.hasRoles() |
Returns true if the user has any roles at all (as opposed to being a basic end user) | true / false |
Practical Example
// onLoad example: customize form based on current user's role
function onLoad() {
// Show the "Assigned to" field as read-only for non-ITIL users
if (!g_user.hasRole('itil')) {
g_form.setReadOnly('assigned_to', true);
g_form.setReadOnly('assignment_group', true);
}
// Pre-fill caller with current user if this is a new record
if (g_form.isNewRecord()) {
g_form.setValue('caller_id', g_user.userID);
}
}
g_user.hasRole('admin') returns true if the user has the admin role OR any role that inherits from admin. g_user.hasRoleExactly('admin') returns true only if the user is directly assigned the admin role — not through role inheritance. The exam may test this distinction.
onChange Parameters & the isLoading Pattern
The onChange script type has a behavior that catches many developers off-guard: it fires for every field on the form when the form first loads, with isLoading = true. Understanding and handling this is critical for both the exam and writing correct scripts.
Parameter Breakdown
| Parameter | Type | Description |
|---|---|---|
control |
Object | The DOM element / control for the field that changed. Rarely used directly — prefer g_form methods. |
oldValue |
String | The value the field had before the change. On initial form load (isLoading=true), this is an empty string. |
newValue |
String | The value the field now has after the change. This is what the user just selected or typed. |
isLoading |
Boolean | true when onChange fires during form initial load (not a real user change). Always check this first. |
isTemplate |
Boolean | true when values are being set from a template. Often treated the same as isLoading. |
The isLoading Guard Pattern
Because onChange fires for every field on form load (with isLoading = true), you typically want to skip processing during the initial load and only react to genuine user-driven changes. The standard pattern is to return early when isLoading is true.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
// Standard guard — skip if firing during initial form load
if (isLoading) return;
if (isTemplate) return; // Optional: also skip template population
// Your logic only runs on genuine user-driven field changes
if (newValue == 'some_value') {
g_form.setMandatory('another_field', true);
} else {
g_form.setMandatory('another_field', false);
}
}
isLoading check is one of the most frequently tested onChange patterns. The exam may ask: "An onChange script is running twice on form load — what should you add?" The answer is always: add if (isLoading) return; at the top of the function.
GlideAjax — Server Calls from Client Scripts
Client Scripts run in the browser and have no direct access to server-side APIs like GlideRecord. When you need server data inside a Client Script (e.g., look up a related record's value), you use GlideAjax to call a server-side Script Include asynchronously.
Asynchronous
GlideAjax calls are always asynchronous. Your script does not pause to wait for the result — it fires the request and continues. All logic that uses the result must be inside the callback function.
Script Include Required
GlideAjax calls a server-side Script Include that must extend AbstractAjaxProcessor and have the Client callable checkbox enabled.
Parameters
Pass data to the Script Include using addParam(). The server-side method reads these using getParameter(). Return the result using setAnswer().
GlideAjax Pattern — Step by Step
Step 1: Create the Script Include (server-side)
// Script Include: AssignmentGroupAjax
// Table: sys_script_include
// Name: AssignmentGroupAjax
// Client callable: CHECKED (must be true)
// Extends: AbstractAjaxProcessor
var AssignmentGroupAjax = Class.create();
AssignmentGroupAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {
// This method will be called from the client
getGroupManager: function() {
// Read the parameter sent from the client
var groupId = this.getParameter('sysparm_group_id');
var gr = new GlideRecord('sys_user_group');
if (gr.get(groupId)) {
var managerName = gr.getDisplayValue('manager');
this.setAnswer(managerName); // Send result back to client
} else {
this.setAnswer('Unknown');
}
},
type: 'AssignmentGroupAjax'
});
Step 2: Call from Client Script (browser-side)
// Client Script (onChange for assignment_group field)
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading) return;
if (!newValue) return;
// Step 1: Create a GlideAjax object, naming the Script Include
var ga = new GlideAjax('AssignmentGroupAjax');
// Step 2: Specify which method in the Script Include to call
ga.addParam('sysparm_name', 'getGroupManager');
// Step 3: Pass parameters to the server method
ga.addParam('sysparm_group_id', newValue);
// Step 4: Send the async request with a callback function
// ALL logic using the result goes INSIDE the callback
ga.getXML(function(response) {
// The callback fires when the server responds
var answer = response.responseXML.documentElement.getAttribute('answer');
g_form.addInfoMessage('Group Manager: ' + answer);
});
// NOTE: Any code here runs BEFORE the callback fires
// Do NOT try to use the result outside the callback
}
- The Script Include must extend AbstractAjaxProcessor
- The Script Include must have Client callable = true
- GlideAjax is always asynchronous — use a callback
- All code using the result must be inside the callback function
- The client cannot call GlideRecord directly — only via GlideAjax
sysparm_namespecifies which method in the Script Include to run
ga.getXML(function(response) { ... }).
Client Scripts vs. UI Policies
Both Client Scripts and UI Policies run in the browser and affect form behavior, but they serve different purposes and have different capabilities. Knowing when to use each is an exam favorite.
| Aspect | Client Script | UI Policy |
|---|---|---|
| Implementation | Full JavaScript code | No-code configuration (point and click) |
| Can set field values | Yes — g_form.setValue() |
Yes (via UI Policy Action) |
| Can make mandatory | Yes — g_form.setMandatory() |
Yes — built-in option |
| Can make read-only | Yes — g_form.setReadOnly() |
Yes — built-in option |
| Can show/hide fields | Yes — g_form.setVisible() |
Yes — built-in option |
| Can call server (GlideAjax) | Yes | No |
| Complex conditions | Full JavaScript — any logic | Condition builder — limited operators |
| Script Include access | Yes (via GlideAjax) | No |
| Ease of maintenance | Requires JavaScript knowledge | Easier — no coding required |
| Best for | Value manipulation, server lookups, complex multi-field logic | Simple mandatory/visible/read-only based on a condition |
Script Includes — Client-Callable
Script Includes are server-side JavaScript libraries stored in the sys_script_include table. A subset of Script Includes can be called from Client Scripts via GlideAjax — these are known as client-callable Script Includes.
Requirements for Client-Callable Script Includes
Client callable = true
The Client callable checkbox on the Script Include record must be checked. Without this, GlideAjax calls to the Script Include will fail with a security error.
Extends AbstractAjaxProcessor
The Script Include class must extend AbstractAjaxProcessor (not the default AbstractScriptInclude). This is what enables the GlideAjax communication pattern.
Use getParameter()
Read parameters sent from the client using this.getParameter('param_name'). By convention, parameter names start with sysparm_.
Use setAnswer()
Return a value to the client using this.setAnswer(value). The client reads this from the XML response: response.responseXML.documentElement.getAttribute('answer').
Script Include Structure Template
// Client-callable Script Include template
// Name: MyAjaxHelper
// Client callable: true (MUST be checked)
// Extends: AbstractAjaxProcessor
var MyAjaxHelper = Class.create();
MyAjaxHelper.prototype = Object.extendsObject(AbstractAjaxProcessor, {
// Public method callable from client via sysparm_name parameter
lookupSomeValue: function() {
var inputParam = this.getParameter('sysparm_my_input');
// Perform server-side logic (GlideRecord, etc.)
var gr = new GlideRecord('some_table');
gr.addQuery('some_field', inputParam);
gr.setLimit(1);
gr.query();
if (gr.next()) {
this.setAnswer(gr.getValue('result_field'));
} else {
this.setAnswer('');
}
// Do NOT return a value — use setAnswer()
},
type: 'MyAjaxHelper'
});
Key Exam Facts & Traps
This section consolidates the highest-probability exam questions and common wrong-answer traps for Client Scripts. Review these carefully before your exam.
- onLoad fires first (once, when the form is rendered)
- onChange fires for each field on the form with
isLoading = true
false in an onSubmit script cancels the save and keeps the user on the form. Returning false in onLoad or onChange has no effect. Only onSubmit and onCellEdit can cancel an action with return false.
g_form.setMandatory('field', true) only enforces mandatory in the browser UI. It can be bypassed via REST API, web services, or any direct server-side insert/update. For true required-field enforcement, use a Business Rule with server-side validation.
ga.getXML(callback) runs before the server responds. This is a frequent wrong-answer pattern: code placed outside the callback will always see undefined/null results.
new GlideRecord() in a Client Script. If you need server data, use GlideAjax to call a Script Include that uses GlideRecord on the server.
g_user.hasRole() use cached data from session initialization. They are NOT authoritative security checks. An attacker could manipulate cached role data in the browser. Real security enforcement must be done server-side.
- Need simple mandatory/visible/read-only based on one condition? → UI Policy
- Need to set a field value dynamically? → Client Script (onChange or onLoad)
- Need to look up server data without a page reload? → Client Script + GlideAjax
- Need to validate before save and possibly cancel? → Client Script (onSubmit)
- Need to react to list-view inline edits? → Client Script (onCellEdit)
- Need true security enforcement (not bypassable)? → ACL or Business Rule