Client Script Patterns for ServiceNow
Client Scripts run in the user's browser and control form behavior. Unlike server-side scripts, client scripts can use modern JavaScript (ES6+) in modern browsers.
Client Script Types
Type When it Runs Use Case
onLoad Form loads Set defaults, hide/show fields, initial setup
onChange Field value changes React to user input, cascading updates
onSubmit Form submitted Validation before save
onCellEdit List cell edited Validate inline edits
The g_form API
Getting and Setting Values
// Get field value var priority = g_form.getValue("priority") var callerName = g_form.getDisplayValue("caller_id") // Reference display value
// Set field value g_form.setValue("priority", "1") g_form.setValue("assigned_to", userSysId, "John Smith") // Reference with display
// Clear a field g_form.clearValue("assignment_group")
Field Visibility and State
// Show/Hide fields g_form.setVisible("u_internal_notes", false) g_form.setDisplay("u_internal_notes", false) // Removes from DOM
// Make field mandatory g_form.setMandatory("short_description", true)
// Make field read-only g_form.setReadOnly("caller_id", true)
// Disable field (grayed out but visible) g_form.setDisabled("state", true)
Messages and Validation
// Field-level messages g_form.showFieldMsg("email", "Invalid email format", "error") g_form.hideFieldMsg("email")
// Form-level messages g_form.addInfoMessage("Record saved successfully") g_form.addErrorMessage("Please fix the errors below") g_form.clearMessages()
// Flash a field to draw attention g_form.flash("priority", "#ff0000", 0) // Red flash
Sections and Labels
// Collapse/Expand sections g_form.setSectionDisplay("notes", false) // Collapse g_form.setSectionDisplay("notes", true) // Expand
// Change field label g_form.setLabelOf("short_description", "Issue Summary")
Common Patterns
Pattern 1: onLoad - Set Defaults
function onLoad() { // Only on new records if (g_form.isNewRecord()) { // Set default priority g_form.setValue("priority", "3")
// Set caller to current user
g_form.setValue("caller_id", g_user.userID)
// Hide internal fields from end users
if (!g_user.hasRole("itil")) {
g_form.setVisible("assignment_group", false)
g_form.setVisible("assigned_to", false)
}
} }
Pattern 2: onChange - Cascading Updates
function onChange(control, oldValue, newValue, isLoading) { // Don't run during form load if (isLoading) return
// When category changes, clear subcategory if (newValue != oldValue) { g_form.setValue("subcategory", "") g_form.clearValue("u_item") }
// Auto-set priority based on category if (newValue == "security") { g_form.setValue("priority", "1") g_form.setReadOnly("priority", true) } else { g_form.setReadOnly("priority", false) } }
Pattern 3: onChange with GlideAjax
function onChange(control, oldValue, newValue, isLoading) { if (isLoading || newValue == "") return
// Get data from server var ga = new GlideAjax("MyScriptInclude") ga.addParam("sysparm_name", "getUserDetails") ga.addParam("sysparm_user_id", newValue) ga.getXMLAnswer(function (response) { var data = JSON.parse(response)
// Update form with server data
g_form.setValue("location", data.location)
g_form.setValue("department", data.department)
g_form.setValue("u_vip", data.vip)
if (data.vip == "true") {
g_form.setValue("priority", "1")
g_form.flash("priority", "#ffff00", 2)
}
}) }
Pattern 4: onSubmit - Validation
function onSubmit() { // Validate email format var email = g_form.getValue("u_email") if (email && !isValidEmail(email)) { g_form.showFieldMsg("u_email", "Please enter a valid email", "error") return false // Prevent submit }
// Require close notes when resolving var state = g_form.getValue("state") var closeNotes = g_form.getValue("close_notes") if (state == "6" && !closeNotes) { g_form.showFieldMsg("close_notes", "Close notes required", "error") g_form.setMandatory("close_notes", true) return false }
// Confirm before high-priority submission var priority = g_form.getValue("priority") if (priority == "1") { return confirm("This will create a Priority 1 incident. Continue?") }
return true // Allow submit }
function isValidEmail(email) { var regex = /^[^\s@]+@[^\s@]+.[^\s@]+$/ return regex.test(email) }
Pattern 5: Conditional Mandatory Fields
function onChange(control, oldValue, newValue, isLoading) { if (isLoading) return
// Category "Hardware" requires asset tag var isHardware = newValue == "hardware" g_form.setMandatory("u_asset_tag", isHardware) g_form.setDisplay("u_asset_tag", isHardware)
// Category "Software" requires application name var isSoftware = newValue == "software" g_form.setMandatory("u_application", isSoftware) g_form.setDisplay("u_application", isSoftware) }
GlideAjax Pattern (Server Communication)
Client Script
function onChange(control, oldValue, newValue, isLoading) { if (isLoading || !newValue) return
var ga = new GlideAjax("IncidentUtils") ga.addParam("sysparm_name", "getRelatedIncidents") ga.addParam("sysparm_ci", newValue) ga.getXMLAnswer(handleResponse) }
function handleResponse(response) { var result = JSON.parse(response)
if (result.count > 0) { g_form.addWarningMessage("There are " + result.count + " related open incidents for this CI") } }
Server Script Include
var IncidentUtils = Class.create() IncidentUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, { getRelatedIncidents: function () { var ci = this.getParameter("sysparm_ci") var result = { count: 0, incidents: [] }
var gr = new GlideRecord("incident")
gr.addQuery("cmdb_ci", ci)
gr.addQuery("active", true)
gr.query()
result.count = gr.getRowCount()
while (gr.next()) {
result.incidents.push({
number: gr.getValue("number"),
short_description: gr.getValue("short_description"),
})
}
return JSON.stringify(result)
},
type: "IncidentUtils", })
g_user Object
// Current user information var userName = g_user.userName // User name var userID = g_user.userID // sys_id var firstName = g_user.firstName // First name var lastName = g_user.lastName // Last name var fullName = g_user.getFullName() // Full name
// Role checks if (g_user.hasRole("admin")) { } if (g_user.hasRole("itil")) { } if (g_user.hasRoleExactly("incident_manager")) { } // Exact match, no admin override
// Multiple roles if (g_user.hasRoleFromList("itil,incident_manager")) { }
Performance Best Practices
- Minimize Server Calls
// ❌ BAD - Multiple GlideAjax calls onChange: getUserLocation() onChange: getUserDepartment() onChange: getUserManager()
// ✅ GOOD - Single call returning all data onChange: getUserDetails() // Returns location, department, manager
- Use isLoading Parameter
function onChange(control, oldValue, newValue, isLoading) { // ❌ BAD - Runs during form load callServer(newValue)
// ✅ GOOD - Skip during load if (isLoading) return callServer(newValue) }
- Debounce Rapid Changes
var timeout function onChange(control, oldValue, newValue, isLoading) { if (isLoading) return
clearTimeout(timeout) timeout = setTimeout(function () { performExpensiveOperation(newValue) }, 300) // Wait 300ms for typing to stop }
Common Mistakes
Mistake Problem Solution
Forgetting isLoading check Script runs unnecessarily on load Always check if (isLoading) return;
Blocking onSubmit UI freezes on slow validation Use async validation with callback
No error handling in GlideAjax Silent failures Add error callbacks
Testing only in one browser Cross-browser issues Test Chrome, Firefox, Edge
Direct DOM manipulation Breaks with UI updates Use g_form API