‹ All essays

ServiceNow

Every Scripting Keyword That Matters in ServiceNow: current, previous, g_form, gs, and the Objects You Must Know

How ServiceNow injects objects into your scripts

When you write a Business Rule, Client Script, or Script Include, ServiceNow wraps your code and injects specific objects depending on the context. You do not import or declare these — they just exist.

current (server-side)

Available in: Business Rules, Workflow scripts, Flow Designer script steps

current is a GlideRecord object pointing to the record being operated on. When a user saves an incident, current is that incident.

// Before Business Rule on incident table
(function executeRule(current, previous) {
    gs.info(current.getValue('number'));        // INC0010001
    gs.info(current.getValue('state'));          // 1
    gs.info(current.getTableName());             // incident
    gs.info(current.isNewRecord());              // true on insert, false on update
    gs.info(current.isActionAborted());          // false (unless someone called setAbortAction)

    // Modify the record before it saves
    current.priority = 1;
    current.work_notes = 'Auto-escalated by Business Rule';

    // Abort the save entirely
    // current.setAbortAction(true);
    // gs.addErrorMessage('Cannot save: missing required data');
})(current, previous);

When current is null

  • Script Includes called directly (not from a BR context) — current is not defined
  • Background Scripts — current is not defined unless you create your own GlideRecord
  • Scheduled Jobs — current is not defined

previous (server-side)

Available in: Business Rules (update and delete operations only)

previous is a read-only GlideRecord snapshot of the record BEFORE the current changes. It lets you compare old vs new values.

(function executeRule(current, previous) {
    // Check if priority changed
    if (current.getValue('priority') != previous.getValue('priority')) {
        gs.info('Priority changed from ' + previous.getValue('priority') +
                ' to ' + current.getValue('priority'));
    }

    // Check if the record was reassigned
    if (current.getValue('assigned_to') != previous.getValue('assigned_to')) {
        gs.info('Reassigned from ' + previous.getDisplayValue('assigned_to') +
                ' to ' + current.getDisplayValue('assigned_to'));
    }
})(current, previous);

When previous is null

  • On insert: previous is null — there is no "before" state for a new record. Always check: if (previous) before using it.
  • In async Business Rules: previous is NOT available. Async BRs run outside the original transaction, so the previous state is gone.
  • In Display Business Rules: previous is not available (the record is being read, not modified).
// Safe pattern — always guard against null previous
(function executeRule(current, previous) {
    if (previous && current.state.changesTo(6)) {
        // State changed to Resolved — only runs on update, not insert
        gs.eventQueue('incident.resolved', current, gs.getUserID(), '');
    }
})(current, previous);

GlideElement methods on current/previous

When you access current.priority, you do not get a number or string. You get a GlideElement object with these methods:

// changes() — did this field change in this transaction?
if (current.priority.changes()) {
    gs.info('Priority was modified');
}

// changesTo(value) — did it change TO this specific value?
if (current.state.changesTo(6)) {
    gs.info('State just changed to Resolved');
}
// NOTE: the value must be the INTERNAL value (number), not the display label.
// changesTo('Resolved') will NOT work. Use changesTo(6).

// changesFrom(value) — did it change FROM this specific value?
if (current.state.changesFrom(1)) {
    gs.info('Was previously New, now something else');
}

// nil() — is the field empty/null?
if (current.assigned_to.nil()) {
    gs.info('Nobody assigned');
}

// toString() — convert GlideElement to string
var desc = current.short_description.toString();

// getDisplayValue() — the human-readable label
gs.info(current.state.getDisplayValue());  // 'New', 'In Progress', etc.

// getReferenceTable() — for reference fields, get the target table
gs.info(current.caller_id.getReferenceTable());  // 'sys_user'

// getRefRecord() — get the full referenced record
var callerGR = current.caller_id.getRefRecord();
gs.info(callerGR.getValue('email'));  // caller's email address

gs (GlideSystem) — the utility belt

Available in: all server-side scripts. gs is a global object with system utility methods.

User context

gs.getUserID();           // sys_id of logged-in user
gs.getUserName();         // 'john.smith'
gs.getUserDisplayName();  // 'John Smith'
gs.hasRole('admin');      // true/false
gs.hasRole('itil');       // true/false

// Get the full user GlideRecord
var userGR = gs.getUser().getRecord();
gs.info(userGR.getValue('email'));

Messages

// These show banners on the user's screen (only in before/after BRs)
gs.addInfoMessage('Record updated successfully.');
gs.addErrorMessage('Cannot save: priority 1 requires an assignment group.');

// These do NOT work in:
// - Async Business Rules (user's transaction is already done)
// - Scheduled Jobs (no user session)
// - Script Includes called from REST APIs

Dates and time

gs.now();                    // '2026-04-07 14:30:00' (user's timezone)
gs.nowDateTime();            // '2026-04-07 14:30:00' (system format)
gs.daysAgo(7);               // date/time 7 days ago
gs.daysAgoStart(7);          // start of day, 7 days ago (midnight)
gs.beginningOfLastMonth();   // first day of previous month
gs.endOfLastMonth();         // last day of previous month

Checking for empty values

// gs.nil() is the safest way to check for empty values
gs.nil(null);       // true
gs.nil(undefined);  // true
gs.nil('');         // true
gs.nil(0);          // false (zero is not nil)
gs.nil('hello');    // false

// Use it to guard against empty fields
var assignee = gr.getValue('assigned_to');
if (gs.nil(assignee)) {
    gs.info('No assignee');
}

g_form (client-side)

Available in: Client Scripts, UI Actions (client-side). NOT available on the server.

g_form controls the form the user is looking at in their browser.

// onChange Client Script
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading) return;  // do not run when the form first loads

    // Read a field value
    var priority = g_form.getValue('priority');  // '1', '2', '3', etc.

    // Set a field value
    g_form.setValue('urgency', '1');

    // Make a field mandatory
    g_form.setMandatory('assignment_group', true);

    // Hide a field
    g_form.setVisible('u_internal_only', false);

    // Show a message
    g_form.addInfoMessage('Priority updated.');
}

// onLoad Client Script — runs when the form opens
function onLoad() {
    if (g_form.isNewRecord()) {
        g_form.setValue('contact_type', 'self-service');
    }
}

// onSubmit Client Script — runs when the user clicks Save
function onSubmit() {
    var desc = g_form.getValue('short_description');
    if (desc.length < 10) {
        g_form.addErrorMessage('Description must be at least 10 characters.');
        return false;  // prevents the form from submitting
    }
    return true;
}

g_user (client-side)

// Available in Client Scripts
g_user.userName;          // 'john.smith'
g_user.userID;            // sys_id of current user
g_user.getFullName();     // 'John Smith'
g_user.hasRole('itil');   // true/false
g_user.hasRoleExactly('incident_manager');  // true only for that specific role

// Common pattern: show/hide fields based on role
function onLoad() {
    if (!g_user.hasRole('itil')) {
        g_form.setVisible('u_technical_notes', false);
    }
}

The client-server boundary

This is the single most important concept in ServiceNow scripting. The server and client are completely separate environments. You cannot call GlideRecord from a Client Script. You cannot call g_form from a Business Rule.

Getting server data from the client: GlideAjax

// Step 1: Create a Script Include on the server
// Name: IncidentUtils
// Must extend AbstractAjaxProcessor to be callable from client
var IncidentUtils = Class.create();
IncidentUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
    getCallerEmail: function() {
        var userId = this.getParameter('sysparm_user_id');
        var gr = new GlideRecord('sys_user');
        if (gr.get(userId)) {
            return gr.getValue('email');
        }
        return '';
    },
    type: 'IncidentUtils'
});

// Step 2: Call it from a Client Script
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || !newValue) return;

    var ga = new GlideAjax('IncidentUtils');
    ga.addParam('sysparm_name', 'getCallerEmail');
    ga.addParam('sysparm_user_id', newValue);
    ga.getXMLAnswer(function(answer) {
        // 'answer' is the string returned by the server method
        g_form.setValue('u_caller_email', answer);
    });
}

// KEY RULES:
// 1. Script Include MUST extend AbstractAjaxProcessor
// 2. Use this.getParameter() to read params on the server
// 3. Always use getXMLAnswer with a callback (async)
// 4. Return values are always strings

Pushing server data to the client: g_scratchpad

// Display Business Rule (server-side, runs on form load)
(function executeRule(current, previous) {
    g_scratchpad.isVIP = String(current.caller_id.vip == true);
    g_scratchpad.openCount = '42';  // must be string/simple types
})(current, previous);

// Client Script (onLoad) — reads g_scratchpad
function onLoad() {
    if (g_scratchpad.isVIP === 'true') {
        g_form.addInfoMessage('This caller is a VIP.');
    }
}
// g_scratchpad avoids a GlideAjax round-trip.
// Use it for data you know you will need on every form load.

Putting it all together: a real example

// GOAL: When a user sets priority to 1 on an incident form,
// make assignment_group mandatory, show a warning,
// and auto-populate the escalation contact from the server.

// === Before Business Rule (server) ===
// Table: incident, When: before, Insert/Update: true
(function executeRule(current, previous) {
    if (current.getValue('priority') == '1' && current.assigned_to.nil()) {
        // Server-side validation — catches API and import submissions too
        current.setAbortAction(true);
        gs.addErrorMessage('P1 incidents must have an assigned person.');
    }
})(current, previous);

// === Client Script (onChange on priority field) ===
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading) return;
    if (newValue == '1') {
        g_form.setMandatory('assignment_group', true);
        g_form.addInfoMessage('P1 selected — assignment group is now required.');

        // Fetch escalation contact from server
        var ga = new GlideAjax('IncidentUtils');
        ga.addParam('sysparm_name', 'getEscalationContact');
        ga.addParam('sysparm_group', g_form.getValue('assignment_group'));
        ga.getXMLAnswer(function(contact) {
            if (contact) {
                g_form.setValue('u_escalation_contact', contact);
            }
        });
    } else {
        g_form.setMandatory('assignment_group', false);
    }
}

// This covers both sides: the client makes the field mandatory
// and shows a message, while the server enforces it for API callers
// who bypass the form entirely.