What It Is
GlideAjax is the client-side JavaScript class that enables asynchronous communication between browser-based scripts and server-side Script Includes in ServiceNow. It solves the fundamental problem of accessing server-side data and business logic from Client Scripts, UI Actions, and UI Pages without forcing a full page reload. When you need to validate data against the database, perform complex calculations, or retrieve dynamic content based on user input, GlideAjax is your bridge between the constrained client-side environment and the full power of the ServiceNow server.
Architecturally, GlideAjax sits at the boundary between ServiceNow's client-side and server-side execution contexts. Your Client Script runs in the user's browser with no direct database access, limited to form manipulation and basic validation. The Script Include it calls executes on the ServiceNow application server with full GlideRecord access, business rule execution, and system API availability. GlideAjax marshals the request across this boundary, handling serialization, authentication, and response formatting automatically.
Under the hood, ServiceNow processes GlideAjax calls through the /ajax_processor.do endpoint. When you call getXML() or getXMLAnswer(), the platform instantiates your Script Include on the server, calls the specified function with your parameters, and returns the result as XML. This execution happens in the same security context as the calling user, with the same ACL restrictions and field-level security applied. The entire round trip typically takes 100-500ms depending on server load and Script Include complexity.
Without GlideAjax, you cannot dynamically populate Reference fields based on form values, validate complex business rules in real-time, or create responsive interfaces that adapt to user input. You're limited to static Client Scripts that work only with data already loaded on the form. Any attempt to query the database, check related records, or perform server-side calculations would require a form submission or page reload, destroying the user experience. GlideAjax is what separates modern ServiceNow interfaces from the clunky, form-based workflows of legacy systems.
Every ServiceNow role uses GlideAjax, but differently. Administrators typically encounter it in pre-built Client Scripts for dynamic field visibility and basic validation. Developers write custom GlideAjax calls for complex form behaviors, cascading dropdowns, and real-time data validation. Architects design GlideAjax patterns for performance-critical interfaces, often caching results and optimizing Script Include execution paths. The complexity scales from simple field population to sophisticated client-server state management.
GlideAjax relates closely to Script Includes, which provide the server-side logic, and Client Scripts, which consume the responses. It's conceptually similar to REST API calls but optimized for ServiceNow's session management and security model. Unlike Catalog Client Scripts that can access GlideCatalogClientScriptAPI for some server-side operations, GlideAjax works everywhere in the platform and provides full server-side API access through your Script Include.
How It Works Under the Hood
When you instantiate a GlideAjax object, you're creating a client-side proxy that will communicate with a specific Script Include on the server. The client-side object maintains a parameter map and handles the HTTP request/response cycle through ServiceNow's built-in AJAX infrastructure. Your Script Include must extend AbstractAjaxProcessor and implement specific method naming conventions to be callable via GlideAjax.
The magic happens in ServiceNow's AJAX processor, which acts as a dispatcher between your client-side calls and server-side Script Includes. When processing your request, the platform validates your session, instantiates the target Script Include, marshals your parameters into the server-side execution context, and captures the response for transmission back to the browser. The entire process maintains transactional integrity and applies the same security controls as any other server-side operation.
The Request Lifecycle
- Client-side JavaScript creates GlideAjax instance with Script Include name
- Parameters added via
addParam()are stored in client-side parameter map - Call to
getXML()triggers HTTP POST to/ajax_processor.do - ServiceNow validates session, instantiates target Script Include on application server
- Server-side method executes with full database access and
gsAPI availability - Return value serialized to XML and transmitted back to browser
- Client-side callback function receives response and processes result
Enjoying this? Get one deep-dive per week.
Join 1,000+ ServiceNow pros — scripts, GlideRecord patterns, Flow Designer techniques, and career moves. Free.
The Core Pattern
function validateAssignment() {
// Get current form values that server needs to validate
var assignedTo = g_form.getValue('assigned_to');
var category = g_form.getValue('category');
// Create GlideAjax instance pointing to our Script Include
var ajax = new GlideAjax('IncidentValidationAjax');
// Set the server-side function to call (sysparm_name is required)
ajax.addParam('sysparm_name', 'validateAssignment');
// Pass form data as parameters to server-side function
ajax.addParam('assigned_to', assignedTo);
ajax.addParam('category', category);
// Execute asynchronous call with callback function
ajax.getXML(function(response) {
var answer = response.responseXML.documentElement.getAttribute('answer');
if (answer !== 'valid') {
g_form.addErrorMessage('Invalid assignment: ' + answer);
}
});
}var IncidentValidationAjax = Class.create();
IncidentValidationAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {
// Method name matches sysparm_name parameter from client
validateAssignment: function() {
// Retrieve parameters sent from client-side
var assignedTo = this.getParameter('assigned_to');
var category = this.getParameter('category');
// Perform server-side validation with full database access
var userGR = new GlideRecord('sys_user');
if (userGR.get(assignedTo)) {
// Check if user has required role for this category
if (!userGR.hasRole('incident_handler')) {
return 'User lacks incident_handler role';
}
}
// Return result to client (accessible via response XML)
return 'valid';
},
type: 'IncidentValidationAjax'
});Real-World Scenarios
Dynamic Location-Based Assignment Groups
When users select a location on incident forms, the assignment group choices need to filter based on groups that actually support that location. The client-side script triggers on location change and populates assignment group choices dynamically.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
return;
}
// Clear existing assignment group when location changes
g_form.clearValue('assignment_group');
var ajax = new GlideAjax('LocationAssignmentAjax');
ajax.addParam('sysparm_name', 'getGroupsForLocation');
ajax.addParam('location_sys_id', newValue);
ajax.addParam('category', g_form.getValue('category'));
ajax.getXML(function(response) {
var groups = response.responseXML.documentElement.getAttribute('answer');
if (groups) {
// Parse JSON response and update assignment group choices
var groupData = JSON.parse(groups);
updateAssignmentGroupChoices(groupData);
}
});
}getGroupsForLocation: function() {
var locationId = this.getParameter('location_sys_id');
var category = this.getParameter('category');
var groups = [];
// Query groups that support this location and category
var groupGR = new GlideRecord('sys_user_group');
groupGR.addQuery('u_supported_locations', 'CONTAINS', locationId);
groupGR.addQuery('u_supported_categories', 'CONTAINS', category);
groupGR.addQuery('active', 'true');
groupGR.query();
while (groupGR.next()) {
groups.push({
sys_id: groupGR.getUniqueValue(),
name: groupGR.getDisplayValue('name')
});
}
return JSON.stringify(groups);
}Watch for performance issues when locations have hundreds of possible groups. Consider caching group relationships in a custom table rather than querying complex many-to-many relationships in real-time. Always validate that the selected assignment group is actually valid for the location server-side.
Real-Time Budget Validation for Procurement
Purchase requests need immediate validation against available budget and existing commitments before submission. Users should see budget status and warnings as they enter line item costs, preventing requests that will fail approval.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '' || isNaN(newValue)) {
return;
}
var costCenter = g_form.getValue('cost_center');
if (!costCenter) {
g_form.addErrorMessage('Select cost center before entering costs');
return;
}
var ajax = new GlideAjax('BudgetValidationAjax');
ajax.addParam('sysparm_name', 'checkBudgetAvailability');
ajax.addParam('cost_center', costCenter);
ajax.addParam('requested_amount', newValue);
ajax.addParam('fiscal_year', new Date().getFullYear());
ajax.getXML(function(response) {
var result = JSON.parse(response.responseXML.documentElement.getAttribute('answer'));
updateBudgetDisplay(result.available, result.committed, result.status);
});
}checkBudgetAvailability: function() {
var costCenter = this.getParameter('cost_center');
var requestedAmount = parseFloat(this.getParameter('requested_amount'));
var fiscalYear = this.getParameter('fiscal_year');
// Get budget allocation for cost center
var budgetGR = new GlideRecord('u_budget_allocation');
budgetGR.addQuery('cost_center', costCenter);
budgetGR.addQuery('fiscal_year', fiscalYear);
budgetGR.query();
if (budgetGR.next()) {
var allocated = parseFloat(budgetGR.getValue('allocated_amount'));
var committed = this.getCommittedAmount(costCenter, fiscalYear);
var available = allocated - committed;
return JSON.stringify({
available: available,
committed: committed,
status: requestedAmount > available ? 'insufficient' : 'ok'
});
}
return JSON.stringify({status: 'no_budget'});
}Budget calculations can be complex with fiscal year boundaries, encumbrances, and partial commitments. Always include the current request ID in committed amount calculations to avoid double-counting during edits.
Cascading Configuration Item Dependencies
When users select a Configuration Item in incident or change forms, related CIs and dependent services should populate automatically. This prevents users from missing critical impact relationships and ensures comprehensive change assessment.
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
g_form.clearValue('related_cis');
return;
}
// Show loading indicator for complex CI relationship queries
g_form.addInfoMessage('Loading CI dependencies...');
var ajax = new GlideAjax('CIDependencyAjax');
ajax.addParam('sysparm_name', 'getRelatedCIs');
ajax.addParam('ci_sys_id', newValue);
ajax.addParam('include_upstream', 'true');
ajax.addParam('include_downstream', 'true');
ajax.addParam('max_depth', '3');
ajax.getXML(function(response) {
g_form.clearMessages();
var dependencies = response.responseXML.documentElement.getAttribute('answer');
if (dependencies) {
populateRelatedCIs(JSON.parse(dependencies));
}
});
}getRelatedCIs: function() {
var ciId = this.getParameter('ci_sys_id');
var maxDepth = parseInt(this.getParameter('max_depth')) || 2;
var dependencies = [];
// Recursive function to traverse CI relationships
var processedCIs = {}; // Prevent infinite loops
function traverseDependencies(currentCI, depth, direction) {
if (depth > maxDepth || processedCIs[currentCI]) return;
processedCIs[currentCI] = true;
var relGR = new GlideRecord('cmdb_rel_ci');
relGR.addQuery(direction === 'upstream' ? 'child' : 'parent', currentCI);
relGR.addQuery('type.name', 'IN', 'Depends on::Used by,Runs on::Runs');
relGR.query();
while (relGR.next()) {
var relatedCI = direction === 'upstream' ? relGR.parent : relGR.child;
dependencies.push({
sys_id: relatedCI.sys_id,
name: relatedCI.name,
type: relatedCI.sys_class_name,
relationship: relGR.type.getDisplayValue(),
depth: depth
});
traverseDependencies(relatedCI, depth + 1, direction);
}
}
traverseDependencies(ciId, 1, 'upstream');
traverseDependencies(ciId, 1, 'downstream');
return JSON.stringify(dependencies);
}CI relationship traversal can hit performance walls quickly. Implement depth limits and consider pre-calculating common dependency trees in scheduled jobs for high-traffic CIs.
The Classic Mistake
Making synchronous calls by forgetting to pass a callback function to addParam() or calling the Script Include directly from the client.
function onLoad() {
// This will NOT work - no callback means synchronous call attempt
var ga = new GlideAjax('IncidentUtils');
ga.addParam('sysparm_name', 'getOpenIncidentCount');
ga.addParam('assigned_to', g_user.userID);
ga.getXML(); // This returns immediately with empty response
// Trying to use the response that doesn't exist yet
var response = ga.getAnswer();
if (response) {
g_form.showFieldMsg('number', 'You have ' + response + ' open incidents', 'info');
}
// This code runs before the server responds
console.log('Response: ' + response); // Always undefined
}This fails because getXML() without a callback returns immediately while the actual HTTP request is still in flight. The browser console shows no errors, but getAnswer() always returns undefined because there's no response data yet. ServiceNow's AJAX implementation is purely asynchronous - there's no way to make it wait for the server response without a callback. The server-side Script Include executes correctly, but by the time it responds, your client-side code has already finished running with empty data.
function onLoad() {
var ga = new GlideAjax('IncidentUtils');
ga.addParam('sysparm_name', 'getOpenIncidentCount');
ga.addParam('assigned_to', g_user.userID);
// Pass callback function - this makes it truly asynchronous
ga.getXML(function(response) {
var answer = response.responseXML.documentElement.getAttribute('answer');
if (answer) {
g_form.showFieldMsg('number', 'You have ' + answer + ' open incidents', 'info');
}
console.log('Response received: ' + answer);
});
// This runs immediately, before server responds
console.log('Request sent, waiting for response...');
}Never call getXML() without a callback function. If you're not passing a function to getXML(), you're doing it wrong and will get empty responses.
Performance Rules
- Limit Script Include queries to under 1000 records using
setLimit()- queries over this threshold cause 30+ second timeouts and angry sys admin emails about slow queries in the System Diagnostics. - Never make more than 5 concurrent
GlideAjaxcalls from a single form - browsers queue requests beyond this limit, causing form freezes and users complaining about unresponsive pages. - Always add indexed field conditions using
addQuery()in your Script Include - unindexed queries on tables with 100K+ records trigger database performance alerts and potential throttling. - Keep response payloads under 1MB by using
JSON.stringify()on minimal data sets - larger responses cause browser memory spikes and mobile app crashes on older devices. - Implement client-side caching with
sessionStoragefor reference data that doesn't change during a user session - repeated calls for the same data waste server resources and slow down form interactions. - Avoid
GlideAjaxcalls inonChange()without debouncing - rapid typing triggers multiple overlapping requests that consume database connections and create race conditions. - Set explicit timeouts using
setTimeout()wrapper around callbacks - hanging requests from network issues leave users staring at loading spinners indefinitely with no error feedback. - Batch multiple related queries into a single Script Include call returning JSON objects - separate
GlideAjaxcalls for related data create unnecessary HTTP overhead and slower page load times.
Side Effects & Platform Behavior
- Script Include execution triggers Business Rules if your code performs database operations - insert/update/delete in Script Includes fire the full BR chain just like form submissions.
- Each
GlideAjaxcall creates entries insyslog_transactiontable with execution time and user context - visible to admins monitoring system performance and user activity. - ACL checks run against any table accessed in the Script Include - users without read access to referenced tables get empty results instead of error messages.
- Using
GlideAjaxin Mobile breaks unless the Script Include is marked asclient_callable=true- mobile apps fail silently with no console errors when this flag is missing. - Session timeout detection fails during
GlideAjaxcalls - expired sessions return login page HTML instead of expected data, causing parsing errors in your callback functions. - Email notifications and Workflows trigger normally if your Script Include modifies records - AJAX calls don't bypass notification rules like you might expect.
- Audit records are created in
sys_audittable for any field changes made through Script Include database operations - auditors can trace AJAX-initiated changes back to the originating user. - Error handling in Script Include methods doesn't automatically propagate to client - exceptions get swallowed and return empty responses unless you explicitly catch and return error messages.
- CSP (Content Security Policy) violations occur when trying to use
eval()onGlideAjaxresponses in instances with strict security policies enabled. - Update Set conflicts arise when multiple developers modify the same Script Include - AJAX calls reference Script Includes by name, so renaming them breaks existing client scripts across the application.
Debugging When It Breaks
The most common failure symptoms are silent failures where your callback never executes, or callbacks that receive undefined responses. Users see forms that appear to load correctly but missing dynamic content, dropdown fields that remain empty, or validation messages that never appear. Developers typically discover the issue when testing reveals that conditional logic isn't working, but the browser console shows no JavaScript errors.
For debugging, start with the browser's Network tab to verify the AJAX request is being sent and receiving a 200 response. Check the Response preview for actual content - you might be getting HTML login pages instead of expected data if your session expired. Move to System Logs > All to find Script Include execution errors, filtering by your Script Include name. The JavaScript Log Statements section shows gs.log() output from your server-side code.
Key error patterns include "ReferenceError: [ScriptIncludeName] is not defined" indicating typos in the Script Include name, "Security constraints prevent access" showing ACL or client callable issues, and XML parsing errors suggesting malformed responses. Response XMLs containing login page HTML instead of expected data indicate session timeouts during the AJAX call.
- Verify Script Include has
client_callablecheckbox checked and method names match exactly between client and server code - Add
console.log(response)as first line in callback function to inspect actual response structure and content - Test Script Include independently using background scripts to isolate server-side logic issues from client-side AJAX problems
- Check user's role permissions for tables accessed in Script Include using ACL debug mode and impersonation
- Confirm no Business Rules or other server-side scripts are interfering by temporarily disabling them during testing
Quick Reference
- Always pass
sysparm_nameparameter matching your Script Include method name exactly - case sensitive - Use
response.responseXML.documentElement.getAttribute('answer')to extract return values in callback functions - Script Include constructor must call
AbstractAjaxProcessor.call(this)and extendAbstractAjaxProcessor - Return complex data as JSON strings using
JSON.stringify()in Script Include andJSON.parse()in callback - Access client parameters in Script Include using
this.getParameter('param_name')- notsysparm_prefix - Client callable Script Includes run as the current user - no elevated privileges unless explicitly granted through roles
- Callback functions execute after form scripts finish - don't reference form variables that might not exist yet
- Mobile compatibility requires Script Include
client_callableflag and simpler callback patterns - avoid complex DOM manipulation - Session timeouts return login page HTML instead of expected XML - always validate response format before processing
- Error handling requires explicit
try/catchblocks in Script Include methods - uncaught exceptions return empty responses