GlideAjax in ServiceNow — Complete Guide with Examples

How GlideAjax works in ServiceNow — making asynchronous server calls from Client Scripts, building Script Includes that extend AbstractAjaxProcessor, and the patterns that work reliably in production.

GlideAjax is how Client Scripts communicate with the server in ServiceNow. It allows you to run server-side code (GlideRecord queries, Script Include methods, system property reads) from a client-side script without refreshing the page. This guide covers how it actually works and the patterns used in production.

Why GlideAjax Exists

Client Scripts run in the browser — they cannot directly access the database, query GlideRecord, or call server-side APIs. GlideAjax bridges that gap by making an asynchronous HTTP request to a server-side Script Include and returning the result to the client callback.

The Two Parts — Script Include and Client Script

Every GlideAjax implementation has two parts:

  1. Server side — a Script Include that extends AbstractAjaxProcessor
  2. Client side — a Client Script that calls the Script Include using GlideAjax

Server Side — Building the Script Include

// Script Include: UserUtils
// Client callable: true (must be checked)
var UserUtils = Class.create();
UserUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {

    // Method called by the client
    getUserManager: function() {
        // Read parameters sent from client
        var userSysId = this.getParameter('sysparm_user_id');
        
        // Do server-side work
        var gr = new GlideRecord('sys_user');
        if (gr.get(userSysId)) {
            // Return data to client
            return gr.getDisplayValue('manager');
        }
        return '';
    },
    
    // Another method on the same Script Include
    getDepartmentUsers: function() {
        var dept = this.getParameter('sysparm_dept');
        var gr = new GlideRecord('sys_user');
        gr.addQuery('department.name', dept);
        gr.addQuery('active', true);
        gr.query();
        
        var users = [];
        while (gr.next()) {
            users.push({
                sys_id: gr.getUniqueValue(),
                name: gr.getDisplayValue('name')
            });
        }
        // Return JSON string for complex data
        return JSON.stringify(users);
    },
    
    type: 'UserUtils'
});

Client Side — Calling the Script Include

// Client Script — onChange on assigned_to field
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || !newValue) return;
    
    // Create GlideAjax object with Script Include name
    var ga = new GlideAjax('UserUtils');
    
    // Specify which method to call
    ga.addParam('sysparm_name', 'getUserManager');
    
    // Pass parameters to the server method
    ga.addParam('sysparm_user_id', newValue);
    
    // Make the async call — callback fires when server responds
    ga.getXMLAnswer(function(answer) {
        // 'answer' is the string returned by the server method
        g_form.setValue('u_manager', answer);
        g_form.setDisplay('u_manager', answer !== '');
    });
}

Returning Complex Data — JSON Pattern

// Client Script calling getDepartmentUsers
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || !newValue) return;
    
    var ga = new GlideAjax('UserUtils');
    ga.addParam('sysparm_name', 'getDepartmentUsers');
    ga.addParam('sysparm_dept', g_form.getDisplayValue('department'));
    
    ga.getXMLAnswer(function(answer) {
        // Parse the JSON string returned by the server
        var users = JSON.parse(answer);
        
        // Use the data — e.g., populate a reference field options
        users.forEach(function(user) {
            console.log(user.sys_id + ': ' + user.name);
        });
    });
}

getXMLAnswer vs getXML

MethodUse when
getXMLAnswer(callback)Server returns a single value via return
getXML(callback)Server uses this.newItem() to return structured XML

For most use cases, getXMLAnswer() with a JSON string return is the cleanest approach.

Critical: Client Callable Must Be Checked

The Script Include must have Client callable checked, otherwise GlideAjax calls will fail with an access error. This is the most common reason GlideAjax calls appear to do nothing — the Script Include is not marked as client callable.

Error Handling

// Server side — return structured response
getUserManager: function() {
    var userSysId = this.getParameter('sysparm_user_id');
    
    if (!userSysId) {
        return JSON.stringify({ error: 'user_id required', data: null });
    }
    
    var gr = new GlideRecord('sys_user');
    if (!gr.get(userSysId)) {
        return JSON.stringify({ error: 'user not found', data: null });
    }
    
    return JSON.stringify({ 
        error: null, 
        data: gr.getDisplayValue('manager') 
    });
},

// Client side — handle errors
ga.getXMLAnswer(function(answer) {
    var result = JSON.parse(answer);
    if (result.error) {
        console.error('GlideAjax error: ' + result.error);
        return;
    }
    g_form.setValue('u_manager', result.data);
});

Performance Consideration

GlideAjax calls are asynchronous — they do not block the UI. However, every call is an HTTP round trip to the server. Avoid making multiple GlideAjax calls in sequence — design your Script Include to return all the data needed in a single call.

// ❌ Two separate server calls
ga1.getXMLAnswer(function(manager) {
    ga2.getXMLAnswer(function(department) {
        // Two round trips
    });
});

// ✅ One call returning all needed data
getUserDetails: function() {
    var gr = new GlideRecord('sys_user');
    gr.get(this.getParameter('sysparm_user_id'));
    return JSON.stringify({
        manager: gr.getDisplayValue('manager'),
        department: gr.getDisplayValue('department'),
        location: gr.getDisplayValue('location')
    });
}

Passing multiple parameters from client to server

// Client Script — passing multiple values
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || !newValue) return;

    var ga = new GlideAjax('LocationUtils');
    ga.addParam('sysparm_name', 'getBuildingInfo');
    ga.addParam('sysparm_location_id', newValue);
    ga.addParam('sysparm_include_floors', 'true');
    ga.addParam('sysparm_format', 'full');

    ga.getXMLAnswer(function(answer) {
        if (!answer) return;
        try {
            var data = JSON.parse(answer);
            g_form.setValue('u_building_name', data.building);
            g_form.setValue('u_building_manager', data.manager_id, data.manager_name);
            if (data.floors) {
                populateFloorDropdown(data.floors);
            }
        } catch(e) {
            console.error('GlideAjax parse error: ' + e);
        }
    });
}
// Script Include — receiving multiple parameters
getBuildingInfo: function() {
    var locationId = this.getParameter('sysparm_location_id');
    var includeFloors = this.getParameter('sysparm_include_floors') === 'true';

    var result = { found: false };
    var gr = new GlideRecord('cmn_location');
    if (gr.get(locationId)) {
        result.found = true;
        result.building = gr.getValue('name');
        result.manager_id = gr.getValue('manager');
        result.manager_name = gr.getDisplayValue('manager');

        if (includeFloors) {
            result.floors = [];
            var fr = new GlideRecord('cmn_building');
            fr.addQuery('location', locationId);
            fr.query();
            while (fr.next()) {
                result.floors.push({
                    value: fr.getUniqueValue(),
                    label: fr.getValue('name')
                });
            }
        }
    }
    return JSON.stringify(result);
}

Error handling on the server side

validateAndLookup: function() {
    var sys_id = this.getParameter('sysparm_sys_id');

    // Validate input before querying
    if (!sys_id || sys_id.length !== 32) {
        return JSON.stringify({ error: 'Invalid sys_id', found: false });
    }

    try {
        var gr = new GlideRecord('incident');
        if (!gr.get(sys_id)) {
            return JSON.stringify({ error: 'Record not found', found: false });
        }
        return JSON.stringify({
            found: true,
            number: gr.getValue('number'),
            state: gr.getDisplayValue('state')
        });
    } catch(e) {
        gs.error('GlideAjax validateAndLookup error: ' + e.getMessage(), 'AjaxUtils');
        return JSON.stringify({ error: 'Server error', found: false });
    }
}

GlideAjax on catalog item forms

GlideAjax works on Service Catalog item variable forms, not just standard forms. The API is identical — the catalog Client Script runs in the browser just like a regular Client Script:

// Catalog Client Script — onCatalogItemValueChange or similar
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading) return;
    var ga = new GlideAjax('ProductUtils');
    ga.addParam('sysparm_name', 'getPriceForSKU');
    ga.addParam('sysparm_sku', newValue);
    ga.getXMLAnswer(function(answer) {
        var price = parseFloat(answer);
        if (!isNaN(price)) {
            g_form.setValue('price', price.toFixed(2));
        }
    });
}

When to use g_scratchpad instead of GlideAjax

For data you know you will need on every form load — regardless of user interaction — a Display Business Rule with g_scratchpad is more efficient. The Display rule runs server-side before the form renders, populating g_scratchpad with data that is then available in onLoad Client Scripts without any additional server round trip. Use GlideAjax for data that is conditionally needed based on user actions (onChange events, user clicking something), and g_scratchpad for data needed unconditionally on form load.

// Display Business Rule (server-side, runs before form loads)
g_scratchpad.userDepartment = current.caller_id.department.getDisplayValue();
g_scratchpad.openIncidentCount = getOpenCountForCaller(current.caller_id.toString());

// onLoad Client Script (reads from scratchpad — no round trip)
function onLoad() {
    var dept = g_scratchpad.userDepartment;
    var count = g_scratchpad.openIncidentCount;
    if (count > 5) {
        g_form.showFieldMsg('caller_id', 'This caller has ' + count + ' open incidents', 'info');
    }
}

Related: GlideAjax complete guide · Script Includes · Client Script types · Business Rules · Debugging scripts

Security considerations for GlideAjax Script Includes

Client-callable Script Includes are exposed to browser-side requests. Any user who can access the ServiceNow instance can call them. Key security practices:

// Safe pattern — validate, check ACL, then return
getUserSensitiveInfo: function() {
    var targetUserId = this.getParameter('sysparm_user_id');

    // Only allow users to look up their own info, or admins to look up any user
    if (targetUserId !== gs.getUserID() && !gs.hasRole('admin')) {
        return JSON.stringify({ error: 'Access denied', found: false });
    }

    var gr = new GlideRecord('sys_user');
    if (gr.get(targetUserId)) {
        return JSON.stringify({
            found: true,
            name: gr.getValue('name'),
            department: gr.getDisplayValue('department')
            // Do NOT include salary, SSN, or other sensitive fields
        });
    }
    return JSON.stringify({ found: false });
}

Debugging GlideAjax calls

GlideAjax issues typically fall into two categories: the server method is not being reached, or it is being reached but returning unexpected data. Debugging approach:

GlideAjax as a career differentiator

Mastering GlideAjax clearly separates administrators from developers on the ServiceNow platform. Administrators configure — they work with forms, lists, workflows, catalog items. Developers build — they construct the client/server interactions that make forms dynamic, responsive, and intelligent. GlideAjax is the bridge. Building working end-to-end GlideAjax examples on your Personal Developer Instance is one of the most effective demonstrations you can bring to a technical interview — a single working example covers Client Scripts, Script Includes, AbstractAjaxProcessor, asynchronous execution, and JSON data transfer, showing breadth across all the areas the CAD exam tests. If you are on the admin-to-developer transition path, build at least three GlideAjax examples that solve real problems — validation against related records, dynamic field population, data lookup on change — and be able to walk through every line of both the server and client sides in an interview context.

Troubleshooting the most common GlideAjax failure modes

Three failure modes account for 90% of GlideAjax issues developers encounter: the Script Include is not client-callable, the method name in sysparm_name has a typo or case mismatch, or the server method is throwing an exception that causes it to return null rather than the expected JSON. The debugging process for all three starts the same way — console.log('raw answer:', answer) in the callback, then check the System Log for server-side errors. If answer is null or undefined, the server method did not execute or returned nothing. If answer is an HTML error page, the Script Include record has a syntax error. If answer is a valid but empty string, the method executed but returned nothing explicitly. See Debugging ServiceNow scripts for the full diagnostic toolkit.

Want the complete reference?

This article is part of the NowSpectrum knowledge library. Browse all products for cheat sheets, interview prep, and deep-dive reference guides.

Browse All Products →
← Back to all posts