GlideAjax in ServiceNow: The Complete Guide

GlideAjax is the bridge between client-side JavaScript running in the browser and server-side code running on the ServiceNow server. It is how Client Scripts fetch database values, check server-side conditions, and call Script Include methods without refreshing the page. This guide covers everything: how GlideAjax works architecturally, building the server-side Script Include, calling from Client Scripts, returning simple values and JSON, multiple methods on one Script Include, error handling, and the patterns used in production.

Why GlideAjax exists

Client Scripts run in the browser. They cannot directly access the ServiceNow database, query GlideRecord, read system properties, or call server-side APIs. Traditionally, developers used synchronous GlideRecord on the client side — but that is deprecated because it blocks the browser's main thread, freezing the UI until the server responds.

GlideAjax provides the asynchronous alternative. The client sends a request to a named server-side Script Include, the server processes it, and the result is delivered to a callback function — without blocking the UI and without a full page reload.

The two parts of every GlideAjax call

Every GlideAjax implementation has exactly two parts:

  1. A server-side Script Include that extends AbstractAjaxProcessor and has the Client callable checkbox checked
  2. A client-side call using the GlideAjax API that references the Script Include by name and calls a method on it

Step 1 — Building the server-side Script Include

Three requirements make a Script Include client-callable:

  • The Script Include record must have Client callable checked
  • The class must extend AbstractAjaxProcessor
  • Methods must use this.getParameter() to read client-supplied values
var IncidentAjaxUtils = Class.create();
IncidentAjaxUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {

    // Method 1 — returns a simple string
    getOpenCountForCaller: function() {
        var callerId = this.getParameter('sysparm_caller_id');
        if (!callerId) return '0';

        var ga = new GlideAggregate('incident');
        ga.addEncodedQuery('active=true^caller_id=' + callerId);
        ga.addAggregate('COUNT');
        ga.query();
        if (ga.next()) {
            return ga.getAggregate('COUNT');
        }
        return '0';
    },

    // Method 2 — returns complex data as JSON
    getIncidentSummary: function() {
        var sysId = this.getParameter('sysparm_sys_id');
        var result = { found: false };

        var gr = new GlideRecord('incident');
        if (gr.get(sysId)) {
            result.found = true;
            result.number = gr.getValue('number');
            result.state = gr.getDisplayValue('state');
            result.priority = gr.getDisplayValue('priority');
            result.assignedTo = gr.getDisplayValue('assigned_to');
            result.shortDesc = gr.getValue('short_description');
            result.openDays = calculateDaysSince(gr.getValue('opened_at'));
        }

        return JSON.stringify(result);
    },

    // Method 3 — lookup by field value
    getUserDepartment: function() {
        var userId = this.getParameter('sysparm_user_id');
        var gr = new GlideRecord('sys_user');
        if (gr.get(userId)) {
            return gr.getDisplayValue('department');
        }
        return '';
    },

    type: 'IncidentAjaxUtils'
});

function calculateDaysSince(dateStr) {
    if (!dateStr) return 0;
    var opened = new GlideDateTime(dateStr);
    var now = new GlideDateTime();
    return Math.floor(GlideDateTime.subtract(opened, now).getDayPart());
}

The getParameter() naming convention

All parameters sent from the client and received by the server must start with sysparm_. This is enforced by the platform — parameters without this prefix are ignored.

// Server side reads with:
this.getParameter('sysparm_my_value');

// Client side sends with:
ga.addParam('sysparm_my_value', someValue);

The name sysparm_name is reserved — it tells the server which method to call. Never use it for your own parameters.

Step 2 — Calling from a Client Script

// In an onChange Client Script
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || !newValue) return;

    // Create the GlideAjax call
    var ga = new GlideAjax('IncidentAjaxUtils'); // Script Include name
    ga.addParam('sysparm_name', 'getOpenCountForCaller'); // Method to call
    ga.addParam('sysparm_caller_id', newValue); // Custom parameter

    // Always async — pass a callback function
    ga.getXMLAnswer(function(answer) {
        // answer is the string returned by the server method
        var count = parseInt(answer);
        if (count > 5) {
            g_form.showFieldMsg('caller_id',
                'This caller has ' + count + ' open incidents',
                'warning');
        } else {
            g_form.hideFieldMsg('caller_id');
        }
    });
}

Calling a JSON-returning method

function onLoad() {
    var ga = new GlideAjax('IncidentAjaxUtils');
    ga.addParam('sysparm_name', 'getIncidentSummary');
    ga.addParam('sysparm_sys_id', g_form.getUniqueValue());

    ga.getXMLAnswer(function(answer) {
        try {
            var data = JSON.parse(answer);
            if (data.found) {
                // Use the rich data from the server
                g_form.showFieldMsg('number',
                    'Open for ' + data.openDays + ' days — Priority: ' + data.priority,
                    'info');
            }
        } catch (e) {
            console.log('GlideAjax JSON parse error: ' + e);
        }
    });
}

getXMLAnswer vs getXML — always use getXMLAnswer

// WRONG — synchronous, blocks the browser, deprecated
var ga = new GlideAjax('MyScriptInclude');
ga.addParam('sysparm_name', 'myMethod');
ga.getXML(); // Never use this — freezes the browser

// RIGHT — asynchronous with callback
ga.getXMLAnswer(function(answer) {
    // handle response here
});

// Also available — getXML with callback (legacy pattern)
ga.getXML(myCallbackFunction);
function myCallbackFunction(response) {
    var answer = response.responseXML.documentElement.getAttribute('answer');
    // process answer
}
// But getXMLAnswer is simpler and preferred

Checking the answer before using it

ga.getXMLAnswer(function(answer) {
    // Always validate before using
    if (!answer || answer === 'null' || answer === 'undefined') {
        console.log('GlideAjax returned empty/null');
        return;
    }

    // For JSON responses, wrap parse in try/catch
    try {
        var data = JSON.parse(answer);
        processData(data);
    } catch (e) {
        console.log('Parse error: ' + e + ' | Raw: ' + answer);
    }
});

Making multiple GlideAjax calls efficiently

If you need multiple pieces of server data, combine them into one GlideAjax call rather than making multiple separate calls. Design the server method to return all needed data as a single JSON object:

// Bad — two separate server round trips
var ga1 = new GlideAjax('UserUtils');
ga1.addParam('sysparm_name', 'getDepartment');
ga1.addParam('sysparm_user_id', userId);
ga1.getXMLAnswer(function(dept) { g_form.setValue('department', dept); });

var ga2 = new GlideAjax('UserUtils');
ga2.addParam('sysparm_name', 'getManager');
ga2.addParam('sysparm_user_id', userId);
ga2.getXMLAnswer(function(mgr) { g_form.setValue('manager', mgr); });

// Good — one round trip, all data returned as JSON
var ga = new GlideAjax('UserUtils');
ga.addParam('sysparm_name', 'getUserContext');
ga.addParam('sysparm_user_id', userId);
ga.getXMLAnswer(function(answer) {
    var data = JSON.parse(answer);
    g_form.setValue('department', data.department);
    g_form.setValue('manager', data.managerId, data.managerName);
    g_form.setValue('location', data.location);
});

GlideAjax vs Display Business Rules

For data you know you will need on every form load, a Display Business Rule with g_scratchpad is more efficient than GlideAjax. Display rules pre-load data before the form renders — no extra request after load. Use GlideAjax for dynamic responses to user interactions (onChange, onSubmit checks) and for conditional data fetching. See our guide on the setValue vs setDisplayValue distinction.

Related guides:

Performance optimisation for GlideAjax

Multiple GlideAjax calls on the same form load are the most common GlideAjax performance anti-pattern. Each call is a round-trip to the server — even if each individual call is fast, five calls on form load means five sequential round trips before the form is fully interactive. The fix is to combine them into one call that returns all needed data as a JSON object:

// BAD — five separate server round trips on form load
function onLoad() {
    loadUserDepartment(userId);
    loadOpenTicketCount(userId);
    loadUserManager(userId);
    loadLocationInfo(locationId);
    loadCategoryOptions();
}

// GOOD — one round trip
function onLoad() {
    var ga = new GlideAjax('FormDataLoader');
    ga.addParam('sysparm_name', 'getFormContext');
    ga.addParam('sysparm_user_id', g_form.getReference('caller_id'));
    ga.addParam('sysparm_location_id', g_form.getValue('location'));
    ga.getXMLAnswer(function(answer) {
        if (!answer) return;
        var data = JSON.parse(answer);
        // Populate all fields from one response
        g_form.setValue('u_department', data.department);
        g_form.setValue('u_manager', data.manager_id, data.managerName);
        if (data.openCount > 3) {
            g_form.showFieldMsg('caller_id', 'VIP caller: ' + data.openCount + ' open tickets', 'info');
        }
    });
}

GlideAjax in catalog variables

GlideAjax works in Service Catalog variable Client Scripts just as it does in form Client Scripts. The API is identical — the key difference is that variable names are used instead of field names when calling g_form.getValue() and g_form.setValue(). Variable names are the field names you defined when creating the catalog item variables. Dynamic population of dropdown variables based on another variable's value (cascading dropdowns) is a common catalog use case for GlideAjax.

AbstractAjaxProcessor deep dive

AbstractAjaxProcessor is not just a requirement for Client callable Script Includes — it provides a set of utility methods that simplify parameter handling and response formatting:

// Available AbstractAjaxProcessor utility methods
this.getParameter('sysparm_name');    // Read a client-supplied parameter
this.getParameterNames();             // Get list of all parameter names
this.newItem('element_name');         // Add an XML element to response
this.setAnswer('value');              // Set the answer attribute in response

// The setAnswer approach (alternative to return)
getIncidentState: function() {
    var sys_id = this.getParameter('sysparm_sys_id');
    var gr = new GlideRecord('incident');
    if (gr.get(sys_id)) {
        this.setAnswer(gr.getValue('state'));
    } else {
        this.setAnswer('');
    }
    // No return needed when using setAnswer
}

Related: Script Includes complete guide · Client Script types · Business Rule types · Debugging · GlideAjax examples

GlideAjax and the broader ServiceNow scripting model

GlideAjax occupies a specific role in the ServiceNow scripting architecture. Business Rules run server-side and cannot interact with the browser UI. Client Scripts run browser-side and cannot access the database directly. GlideAjax is the explicit, controlled bridge between these two worlds — a mechanism for client-side scripts to request specific data from the server without either directly accessing the database from the browser (deprecated and removed) or forcing a full page reload (which would reset UI state). Understanding this architectural role — not just the API — is what distinguishes developers who use GlideAjax correctly from those who use it as a workaround. If you find yourself making many GlideAjax calls on every form load for data that never changes based on user interaction, consider whether a Display Business Rule with g_scratchpad would be more appropriate. If you find yourself skipping GlideAjax and using deprecated synchronous GlideRecord in Client Scripts because it is simpler, recognise that the deprecated pattern exists precisely because it causes the performance and UX problems you are probably already seeing. GlideAjax is the right tool for its use case — asynchronous, server-data requests triggered by user interaction.

GlideAjax is the bridge that makes ServiceNow forms dynamic and responsive. Master it alongside Script Includes, Client Scripts, and Business Rules — together these four APIs cover the full spectrum of platform scripting. The GlideAjax examples guide provides more working implementations you can study and adapt. The interview questions guide covers how GlideAjax is tested at mid and senior levels.

GlideAjax common mistakes — checklist

Before deploying a GlideAjax implementation: confirm the Script Include has Client callable checked, confirm sysparm_name matches the method name exactly (case-sensitive), confirm the method returns a string (not an object — JSON.stringify() before returning if passing an object), confirm the callback handles both the success case and the empty/null response case, and confirm there are no gs.log() calls inside the Script Include method that might interfere with the response (they do not, but they can clutter the System Log — use them during development, remove before deployment). See debugging guide for the systematic diagnostic process.

50 scripting patterns in one guide

The NowSpectrum Pro Tips and Tricks guide covers 50 battle-tested patterns including GlideAjax, Client Scripts, Script Includes, and GlideRecord.

Get the Pro Tips Guide →
← Back to all posts