ServiceNow
JavaScript Inside ServiceNow: var, let, const, Types, and What Actually Runs Where
The first thing to know
ServiceNow runs two completely separate JavaScript environments. Code that works on one side will crash on the other.
| Server-side | Client-side | |
|---|---|---|
| Runs in | ServiceNow's Java-based server (Rhino engine) | The user's web browser |
| Script types | Business Rules, Script Includes, Scheduled Jobs, Fix Scripts, Background Scripts | Client Scripts, UI Policies (scripted), UI Actions (client) |
| JS version | ES5 (no let/const, no arrow functions, no template literals in global scope) | Modern ES6+ (depends on the user's browser) |
| Database access | Yes — GlideRecord, GlideAggregate | No — must use GlideAjax to call server |
| Available objects | current, previous, gs, GlideRecord | g_form, g_user, g_list |
Variables: var, let, const
Server-side (Business Rules, Script Includes)
The server uses the Rhino engine which only supports ES5 in global scope. That means:
// This works everywhere on the server:
var name = 'incident';
var count = 0;
var isActive = true;
// This FAILS in global-scope server scripts:
// let name = 'incident'; // SyntaxError
// const MAX = 100; // SyntaxError
// var msg = `Hello ${name}`; // SyntaxError — no template literals
// EXCEPTION: Scoped applications run on a newer engine (ES2021+)
// In scoped apps, let/const/arrow functions DO work:
// let x = 10; // works in scoped app scripts
// const y = 20; // works in scoped app scripts
Rule of thumb: In global scope, use var. In scoped applications, you can use let and const. When in doubt, use var — it works everywhere.
Client-side (Client Scripts)
Client scripts run in the browser, so modern JS works fine:
// All of these work in Client Scripts:
var oldWay = 'works';
let newWay = 'also works';
const CONSTANT = 'works too';
var msg = `Hello ${oldWay}`; // template literals work
var vs let — the actual difference
// var is function-scoped — it leaks out of blocks
for (var i = 0; i < 5; i++) {
// i is visible here
}
gs.info(i); // 5 — var leaks out of the for loop
// let is block-scoped — it stays inside the block
for (let j = 0; j < 5; j++) {
// j is visible here
}
// gs.info(j); // ReferenceError — j does not exist here
// const — like let but cannot be reassigned
const MAX_RETRIES = 3;
// MAX_RETRIES = 5; // TypeError — cannot reassign
Data types
// String
var desc = 'Server is down';
var desc2 = "Also a string"; // single or double quotes both work
// Number (integer and decimal are the same type)
var priority = 1;
var percent = 99.5;
// Boolean
var isActive = true;
var isClosed = false;
// Null and Undefined
var assigned = null; // explicitly set to nothing
var notSet; // undefined — never assigned a value
// gs.nil() checks for null, undefined, AND empty string:
gs.info(gs.nil(null)); // true
gs.info(gs.nil(undefined)); // true
gs.info(gs.nil('')); // true
gs.info(gs.nil('hello')); // false
// Array
var priorities = [1, 2, 3, 4];
gs.info(priorities[0]); // 1
gs.info(priorities.length); // 4
// Object
var config = {
table: 'incident',
limit: 50,
active: true
};
gs.info(config.table); // 'incident'
gs.info(config['limit']); // 50 — bracket notation also works
Type gotchas in ServiceNow
GlideRecord fields do not return plain JavaScript types. They return GlideElement objects. This causes bugs that are hard to spot.
var gr = new GlideRecord('incident');
gr.get('number', 'INC0010001');
// WRONG — comparing a GlideElement to a string
if (gr.priority == '1') {
// This MIGHT work, but it is unreliable.
// gr.priority is a GlideElement object, not a string.
}
// CORRECT — use getValue() to get the raw string value
if (gr.getValue('priority') == '1') {
// This always works. getValue returns a plain string.
}
// WRONG — concatenating GlideElement into a string
var msg = 'Priority is ' + gr.priority;
// This calls toString() on the GlideElement, which usually works
// but can behave unexpectedly with reference fields.
// CORRECT — explicit getValue
var msg = 'Priority is ' + gr.getValue('priority');
// WRONG — storing a GlideElement in a variable for later use
var saved = gr.caller_id;
gr.next(); // moves to next record
gs.info(saved); // NOW POINTS TO THE NEW RECORD'S caller_id!
// GlideElement is a reference to the cursor position, not a copy.
// CORRECT — store the value, not the element
var saved = gr.getValue('caller_id');
gr.next();
gs.info(saved); // still has the original value
Functions
// Basic function (works everywhere)
function calculatePriority(impact, urgency) {
if (impact == 1 && urgency == 1) return 1;
if (impact == 1 || urgency == 1) return 2;
return 3;
}
var p = calculatePriority(1, 2); // returns 2
// Function in a Business Rule (the wrapper pattern)
// ServiceNow wraps your BR code in an IIFE:
(function executeRule(current, previous) {
// 'current' = the record being saved
// 'previous' = the record before changes (null on insert)
current.priority = calculatePriority(
current.getValue('impact'),
current.getValue('urgency')
);
})(current, previous);
// Arrow functions — ONLY in scoped apps or client scripts
// var calc = (a, b) => a + b; // fails in global server scope
// Use regular functions on the server in global scope.
Loops
// for loop — iterate a known number of times
for (var i = 0; i < 10; i++) {
gs.info('Iteration ' + i);
}
// while loop — the GlideRecord pattern
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.setLimit(100);
gr.query();
while (gr.next()) {
gs.info(gr.getValue('number'));
}
// gr.next() returns false when there are no more records
// for...in — iterate over object keys
var config = { table: 'incident', limit: 50 };
for (var key in config) {
gs.info(key + ' = ' + config[key]);
}
// Output: table = incident, limit = 50
// Array iteration
var items = ['one', 'two', 'three'];
for (var i = 0; i < items.length; i++) {
gs.info(items[i]);
}
// .forEach works in scoped apps and client scripts:
// items.forEach(function(item) { gs.info(item); });
Conditionals
// if / else if / else
var priority = gr.getValue('priority');
if (priority == '1') {
gs.info('Critical');
} else if (priority == '2') {
gs.info('High');
} else {
gs.info('Normal or lower');
}
// == vs === (loose vs strict equality)
gs.info(1 == '1'); // true — loose comparison, converts types
gs.info(1 === '1'); // false — strict comparison, different types
// In ServiceNow, getValue() always returns strings, so:
// gr.getValue('priority') == 1 → true (loose, works)
// gr.getValue('priority') === 1 → false (strict, different types)
// gr.getValue('priority') === '1' → true (strict, both strings)
// Best practice: use == when comparing getValue output to numbers,
// or convert explicitly: Number(gr.getValue('priority')) === 1
// Ternary operator
var label = (priority == '1') ? 'Critical' : 'Not critical';
// Switch statement
switch (gr.getValue('state')) {
case '1': gs.info('New'); break;
case '2': gs.info('In Progress'); break;
case '6': gs.info('Resolved'); break;
case '7': gs.info('Closed'); break;
default: gs.info('Other state');
}
Strings
var desc = 'Email server down in building A';
desc.length; // 33
desc.indexOf('server'); // 6 (position of first match)
desc.indexOf('xyz'); // -1 (not found)
desc.includes('server'); // true (scoped apps/client only)
desc.toLowerCase(); // 'email server down in building a'
desc.toUpperCase(); // 'EMAIL SERVER DOWN IN BUILDING A'
desc.substring(0, 5); // 'Email'
desc.split(' '); // ['Email', 'server', 'down', 'in', 'building', 'A']
desc.replace('down', 'offline'); // 'Email server offline in building A'
desc.trim(); // removes leading/trailing whitespace
// String concatenation
var msg = 'Incident ' + gr.getValue('number') + ' is priority ' + gr.getValue('priority');
// Template literals (scoped apps and client scripts only)
// var msg = `Incident ${number} is priority ${priority}`;
Arrays
var groups = ['Network', 'Database', 'Security'];
groups.length; // 3
groups[0]; // 'Network'
groups.push('Storage'); // adds to end: ['Network','Database','Security','Storage']
groups.pop(); // removes last: ['Network','Database','Security']
groups.indexOf('Database'); // 1
groups.join(', '); // 'Network, Database, Security'
// Check if value exists in array
if (groups.indexOf('Network') !== -1) {
gs.info('Found it');
}
// Build an array from GlideRecord results
var numbers = [];
var gr = new GlideRecord('incident');
gr.addQuery('priority', 1);
gr.setLimit(10);
gr.query();
while (gr.next()) {
numbers.push(gr.getValue('number'));
}
gs.info('P1 incidents: ' + numbers.join(', '));
Objects / JSON
// Create an object
var incident = {
number: 'INC0010001',
priority: 1,
active: true,
tags: ['network', 'vpn']
};
// Access properties
gs.info(incident.number); // 'INC0010001'
gs.info(incident['priority']); // 1
// Add/modify properties
incident.state = 'New';
incident.priority = 2;
// Convert to JSON string (for logging, REST payloads, etc.)
var jsonStr = JSON.stringify(incident);
gs.info(jsonStr);
// {"number":"INC0010001","priority":2,"active":true,"tags":["network","vpn"],"state":"New"}
// Parse JSON string back to object
var parsed = JSON.parse(jsonStr);
gs.info(parsed.number); // 'INC0010001'
// Common use: REST API responses
var response = new sn_ws.RESTMessageV2('MyAPI', 'GET');
var body = response.execute().getBody();
var data = JSON.parse(body); // parse response body into usable object
Error handling
// try/catch — prevent your script from crashing the entire transaction
try {
var gr = new GlideRecord('incident');
gr.get('number', 'INC0010001');
var result = JSON.parse(gr.getValue('u_custom_json_field'));
gs.info(result.key);
} catch (e) {
gs.error('Failed to parse JSON: ' + e.message);
// The script continues instead of crashing
}
// try/catch/finally
try {
// risky operation
} catch (e) {
gs.error('Error: ' + e.message);
} finally {
// runs no matter what — cleanup code goes here
}
// When to use try/catch:
// - Parsing JSON from external systems (malformed data)
// - REST API calls (network failures)
// - Any operation where bad data could crash your Business Rule
Scope — the #1 confusion point
// Variables declared with var inside a function are LOCAL to that function
function myFunction() {
var localVar = 'only exists here';
gs.info(localVar); // works
}
// gs.info(localVar); // ReferenceError — does not exist outside
// Variables declared outside any function are GLOBAL to the script
var globalVar = 'visible everywhere in this script';
function test() {
gs.info(globalVar); // works — can see the outer variable
}
// In Business Rules, 'current' and 'previous' are provided by the platform.
// You do not declare them. They are injected into the script context:
(function executeRule(current, previous) {
// current = the record being modified
// previous = the record BEFORE your changes (null on insert)
// Both are GlideRecord objects pointing to the current row
})(current, previous);
What does NOT work on the server
// These are browser/Node.js features. They do not exist on the ServiceNow server.
// setTimeout / setInterval — no timers on server
// setTimeout(function() { }, 1000); // does not exist
// console.log — use gs.info instead
// console.log('test'); // does not exist
// document / window / DOM — there is no browser on the server
// document.getElementById('test'); // does not exist
// fetch / XMLHttpRequest — use RESTMessageV2 instead
// fetch('https://api.example.com'); // does not exist
// require / import — use gs.include for Script Includes
// var lib = require('mylib'); // does not exist
// Promise / async / await — not available in global scope server scripts
// Use GlideHTTPRequest or synchronous REST calls
Quick reference: where to write each script type
| I want to... | Write a... | Navigate to |
|---|---|---|
| Validate data before save | Before Business Rule | System Definition > Business Rules |
| Create related records after save | After Business Rule | System Definition > Business Rules |
| Show/hide fields on a form | Client Script (onLoad/onChange) or UI Policy | System Definition > Client Scripts |
| Validate before form submit | Client Script (onSubmit) | System Definition > Client Scripts |
| Reusable server function | Script Include | System Definition > Script Includes |
| Call server from client | GlideAjax + Script Include (extends AbstractAjaxProcessor) | Both |
| Run something on a schedule | Scheduled Job | System Definition > Scheduled Jobs |
| One-time data cleanup | Fix Script or Background Script | System Definition > Fix Scripts, or Scripts - Background |
| Custom button on a form | UI Action | System Definition > UI Actions |