GlideAjax: Asynchronous Client-Server Communication
Introduction
Client Scripts run in the browser and have no direct access to the ServiceNow database. When a Client Script needs server-side data — a lookup, a calculation, a permission check — the correct mechanism is GlideAjax: an asynchronous, callback-driven communication pattern that queries the server without blocking the browser.
This guide covers everything from basic GlideAjax usage to advanced patterns for multiple values and error handling.
The Core Pattern
GlideAjax requires two components:
- A Script Include that extends
AbstractAjaxProcessor(server-side handler) - A Client Script that instantiates
GlideAjaxand provides a callback (client-side caller)
Server Side: AbstractAjaxProcessor
// Script Include: IncidentAjaxHelper
// Client callable: ✅ Checked
var IncidentAjaxHelper = Class.create();
IncidentAjaxHelper.prototype =
Object.extendsObject(AbstractAjaxProcessor, {
// Each public method is callable via sysparm_name
getCallerInfo: function() {
var userId = this.getParameter('sysparm_user_id');
var user = new GlideRecord('sys_user');
if (!user.get(userId)) {
return JSON.stringify({ error: 'User not found' });
}
return JSON.stringify({
name: user.getDisplayValue('name'),
email: user.getValue('email'),
department: user.getDisplayValue('department'),
vip: user.getValue('vip') === '1'
});
},
getOpenIncidentCount: function() {
var assignee = this.getParameter('sysparm_assignee');
var ga = new GlideAggregate('incident');
ga.addQuery('assigned_to', assignee);
ga.addQuery('active', true);
ga.addAggregate('COUNT');
ga.query();
return ga.next() ? ga.getAggregate('COUNT') : '0';
},
type: 'IncidentAjaxHelper'
});
Critical settings on the Script Include record:
- Client callable: Must be checked ✅
- Name: Must match the class name exactly
Client Side: Making the Call
// Client Script — onChange on 'assigned_to' field
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading || !newValue) return;
var ga = new GlideAjax('IncidentAjaxHelper');
ga.addParam('sysparm_name', 'getOpenIncidentCount'); // Method to call
ga.addParam('sysparm_assignee', newValue); // Parameters
// ALWAYS use async callback — never ga.getXML() synchronously
ga.getXMLAnswer(function(answer) {
var count = parseInt(answer);
if (count > 10) {
g_form.addInfoMessage(
'Warning: This agent has ' + count +
' open incidents. Consider reassigning.'
);
}
});
}
Returning Complex Data (JSON)
For multiple values, return JSON and parse on the client:
// Client Script
var ga = new GlideAjax('IncidentAjaxHelper');
ga.addParam('sysparm_name', 'getCallerInfo');
ga.addParam('sysparm_user_id', g_form.getValue('caller_id'));
ga.getXMLAnswer(function(answer) {
try {
var info = JSON.parse(answer);
if (info.error) {
g_form.addErrorMessage('Could not load caller info.');
return;
}
if (info.vip) {
g_form.addInfoMessage('VIP Caller: ' + info.name +
' — handle with priority care.');
g_form.flash('caller_id', '#FFD700', 0);
}
// Populate a display field
g_form.setValue('u_caller_department', info.department);
} catch(e) {
gs.error('GlideAjax parse error: ' + e.message);
}
});
getXML vs. getXMLAnswer vs. getXMLWait
| Method | Behavior | Use |
|---|---|---|
getXML(callback) |
Async, callback receives full XML response object | When you need raw XML or multiple return values |
getXMLAnswer(callback) |
Async, callback receives the answer string directly |
Most common — cleaner for single string returns |
getXMLWait() |
Synchronous — blocks the browser | Never use. Deprecated and performance-killing |
Chaining Multiple GlideAjax Calls
When you need sequential server calls, chain them in callbacks:
function onLoad() {
// First call: get caller's VIP status
var ga1 = new GlideAjax('IncidentAjaxHelper');
ga1.addParam('sysparm_name', 'getCallerInfo');
ga1.addParam('sysparm_user_id', g_form.getValue('caller_id'));
ga1.getXMLAnswer(function(answer) {
var info = JSON.parse(answer);
if (info.vip) {
// Second call: get VIP's preferred contact method
var ga2 = new GlideAjax('IncidentAjaxHelper');
ga2.addParam('sysparm_name', 'getVIPPreferences');
ga2.addParam('sysparm_user_id', g_form.getValue('caller_id'));
ga2.getXMLAnswer(function(prefs) {
var preferences = JSON.parse(prefs);
g_form.setValue('contact_type', preferences.preferredContact);
});
}
});
}
Security Considerations
Since Script Includes marked as "Client callable" are accessible to any browser session:
- Always validate the
sysparm_nameparameter in complex Script Includes - Check caller permissions within the method if the data is sensitive
- Never return data the caller shouldn't have access to, regardless of how they call it
getSensitiveData: function() {
// Validate the caller has appropriate role
if (!gs.hasRole('itil_admin')) {
return JSON.stringify({ error: 'Access denied' });
}
// ... return sensitive data
}
Best Practices
- Never use
getXMLWait()— always async callbacks - Return JSON for multiple values; single string for simple values
- Always parse JSON in a try/catch block
- Validate permissions inside client-callable Script Includes
- Name Script Include methods descriptively (verb + noun)
- Keep Script Include methods focused — one method, one purpose
- Test the Script Include server-side before writing the client call
Conclusion
GlideAjax is the safe, correct way to bridge the gap between client-side form logic and server-side data. The pattern is simple — a client-callable Script Include on the server, a callback-driven GlideAjax call on the client — but the details matter. Master JSON serialization, embrace asynchronous callbacks, and validate permissions in every public method, and your client scripts will be both powerful and secure.