CSA Hub Part 6 Client Scripts
⚙️ Part 6 · Topic 3 of 4

Client Scripts

⏱ ~70 min read · Part 6 — Self-Service & Automation · 20% Exam Weight
Heavily Tested: Client Scripts are among the most frequently tested topics on the CSA exam. Expect multiple questions on script types, g_form API methods, GlideAjax usage, and the isLoading pattern. Master every section here.

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.

Key Principle: Because Client Scripts run in the browser, they affect only the current user's session. They cannot be used as security controls — any client-side restriction can be bypassed via REST API or direct database access. Use ACLs and Business Rules for real security enforcement.

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

Fires when: The form is displayed to the user — before any user interaction occurs.
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

Fires when: A specific field's value changes (field must be specified in the "Field name" property of the script).
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

Fires when: The user clicks Save, Update, or Submit — immediately before the record is saved to the server.
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

Fires when: A user edits a cell directly in a list view (not on a form). This is the inline list editing mode.
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
Exam Must-Know: Only onSubmit and onCellEdit can cancel an action by returning false. Returning false in onLoad or onChange has no effect. Also remember: onCellEdit fires in list view, not on a form — this distinction is frequently tested.

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 valuevoid
clearValue('field')Empty a fieldvoid
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/optionalvoid
setReadOnly('field', bool)Make field read-only/editablevoid
addInfoMessage('msg')Show blue info bannervoid
addErrorMessage('msg')Show red error bannervoid
clearMessages()Remove all form bannersvoid
showFieldMsg('field', 'msg', 'type')Show message under a fieldvoid
getControl('field')Get the DOM element for a fieldDOM Element
getLabel('field')Get the label text of a fieldString
isNewRecord()Check if this is a new recordBoolean
getSysId()Get the sys_id of the current recordString
getTableName()Get the table name of the current formString
Exam Trap — setMandatory vs ACL: 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.

Important: 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);
    }
}
hasRole vs hasRoleExactly: 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);
    }
}
Critical Exam Point: The 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.
When You DO Want isLoading Behavior: Sometimes you want onChange to also run on form load — for example, to set initial visibility based on a field's default value that was already set. In that case, you intentionally omit the isLoading guard, or handle both cases with separate logic blocks.

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
}
GlideAjax Exam Rules:
  1. The Script Include must extend AbstractAjaxProcessor
  2. The Script Include must have Client callable = true
  3. GlideAjax is always asynchronous — use a callback
  4. All code using the result must be inside the callback function
  5. The client cannot call GlideRecord directly — only via GlideAjax
  6. sysparm_name specifies which method in the Script Include to run
Common Mistake: Trying to use the GlideAjax result outside the callback. Because the call is async, the variable will still be undefined when the next line of code runs. Always put your result-dependent logic inside 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
Best Practice: Always prefer UI Policies for simple field behavior (mandatory, visible, read-only) triggered by a single condition. They are easier to read, maintain, and debug. Use Client Scripts when you need JavaScript logic: setting computed values, calling the server via GlideAjax, or handling complex multi-field interactions.
Exam Question Pattern: "A field should be mandatory when Priority = Critical. What is the BEST approach?" The answer is UI Policy — not a Client Script — because it is a simple condition with no need for JavaScript or server calls.

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'
});
Security Note: Even though a Script Include is marked "Client callable," ACLs and other security controls still apply to the underlying data it queries. The Script Include simply enables the communication channel — it does not bypass data security.

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.

Fact 1 — Execution Order: When a form loads, scripts execute in this order:
  1. onLoad fires first (once, when the form is rendered)
  2. onChange fires for each field on the form with isLoading = true
This is why the isLoading guard is essential — onChange fires during load, NOT just when a user changes a field.
Fact 2 — Return false in onSubmit: Returning 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.
Fact 3 — setMandatory ≠ Security: 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.
Fact 4 — GlideAjax is ASYNC: All code that depends on the GlideAjax result must be inside the callback. Code after 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.
Fact 5 — No GlideRecord in Client Scripts: GlideRecord is a server-side API. You cannot use new GlideRecord() in a Client Script. If you need server data, use GlideAjax to call a Script Include that uses GlideRecord on the server.
Fact 6 — onCellEdit is List-Only: onCellEdit fires when a user edits a cell in list view inline editing. It does NOT fire on a form. If you want to react to a field change on a form, use onChange.
Fact 7 — g_user.hasRole() is Cached: Client-side role checks via 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.
Fact 8 — Script Include Requirements: For GlideAjax to work, the Script Include must: (1) have Client callable = true, and (2) extend AbstractAjaxProcessor. Missing either one will cause the GlideAjax call to fail.
Quick Decision Guide:
  • 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
← Business Rules Flow Designer →