What is a variable? Think of it like a labeled box. You put something inside (a number, some text, etc.) and use the label to find it later. In JavaScript, we have three ways to create these boxes: var, let, and const.
const = the box is sealed. You can't replace what's inside. Use this by default — it prevents accidental changes.
let = the box is open. You can swap what's inside. Use this when you need to change the value later.
var = the old way. Don't use it — it has weird scoping rules that cause bugs.
var — Avoid This
var name = "John";
var name = "Jane"; // No error! Silently overwrites
if (true) {
var x = 5; // Leaks outside the if-block!
}
console.log(x); // 5 — even though x was inside the iflet & const — Always Use These
const name = "John";
name = "Jane"; // TypeError! Can't reassign const
let score = 0;
score = 100; // OK! let allows reassignment
if (true) {
let y = 5; // Stays inside the if-block
}
console.log(y); // ReferenceError! y doesn't exist hereJavaScript stores information in different "types." Why does it matter? Because the type determines what you can DO with the data. You can multiply two numbers, but you can't multiply two strings.
| Type | What It Is | Example | In One Sentence |
|---|---|---|---|
| Number | Any number (integer or decimal) | 42, 3.14, -7 | Used for counting, measuring, calculating |
| String | Text, wrapped in quotes | "hello", 'world' | Used for names, descriptions, messages |
| Boolean | True or false | true, false | Used for yes/no decisions |
| Undefined | Variable declared but no value given | let x; | "I created the box but haven't put anything in it" |
| Null | Intentionally empty | let x = null; | "I deliberately put nothing in this box" |
| Object | A collection of key-value pairs | {name: "John"} | Like a contact card with multiple fields |
| Array | An ordered list of items | [1, 2, 3] | Like a shopping list — items in order |
Number = a thermometer reading. String = a name tag. Boolean = a light switch. Undefined = an empty shelf. Null = a shelf labeled "empty on purpose." Object = a file folder with labeled tabs. Array = a numbered list.
Use typeof to ask JavaScript "what type is this?" It returns a string telling you the type. Super useful for debugging!
typeof 42 // "number"
typeof "hello" // "string"
typeof true // "boolean"
typeof undefined // "undefined"
typeof null // "object" ← This is a famous JS bug! null is NOT an object.
typeof {} // "object"
typeof [1,2] // "object" ← Arrays also say "object". Use Array.isArray() instead.
typeof function(){} // "function"== (double equals) checks if two values are "sort of equal" — it converts types first. === (triple equals) checks if they are exactly equal — same value AND same type. Always use ===. It prevents bugs.
== (Loose) — Unpredictable
1 == "1" // true — JS converts string to number!
0 == false // true — 0 and false are both "falsy"
"" == 0 // true — empty string equals zero??
null == undefined // true — they're different types though!=== (Strict) — Predictable
1 === "1" // false — number is NOT a string ✅
0 === false // false — number is NOT boolean ✅
"" === 0 // false — string is NOT number ✅
null === undefined // false — null is NOT undefined ✅When you compare field values from GlideRecord, they're often strings. current.getValue('priority') returns a string like "1", not the number 1. That's why === matters — you need to know if you're comparing strings or numbers.
&& and || don't just return true/false. They return the actual value that decided the result. This is incredibly useful for providing defaults or conditional assignments.
// || returns the FIRST truthy value (or the last one if all falsy)
const name = "" || "Anonymous"; // "Anonymous" — "" is falsy, so skip it
const role = "Admin" || "User"; // "Admin" — "Admin" is truthy, return it
// && returns the FIRST falsy value (or the last one if all truthy)
const result = true && "success"; // "success" — both truthy, return last
const fail = 0 && "hello"; // 0 — 0 is falsy, return it and stop
// ?? (nullish coalescing) — like || but ONLY null/undefined trigger fallback
const count = 0 ?? 10; // 0 — 0 is valid, not null/undefined
const val = null ?? 10; // 10 — null triggers the fallback
// ServiceNow example: provide a default category
const cat = current.getValue('category') || 'Inquiry';Instead of writing a 5-line if/else just to assign a value, use the ternary: condition ? valueIfTrue : valueIfFalse. It's like asking a question: "Is this true? If yes, give me A. If no, give me B."
// Long way:
let label;
if (priority === "1") {
label = "Critical";
} else {
label = "Normal";
}
// Short way (ternary):
const label = priority === "1" ? "Critical" : "Normal";
// ServiceNow example:
const msg = current.getValue('state') === '6'
? 'Incident resolved'
: 'Still open';A string is just text — anything wrapped in quotes. You'll work with strings constantly in ServiceNow because almost every field value you read is a string. Here's what you need to know.
Template literals use backticks (``) instead of regular quotes. The magic? You can embed variables directly inside with ${variableName}. No more + + + everywhere.
Old Way — Hard to Read
var msg = "Incident " + incNum + " was created by " + caller + " on " + date;New Way — Clean and Clear
const msg = `Incident ${incNum} was created by ${caller} on ${date}`;// You can also put expressions inside ${}
const total = `Total: $${(price * quantity).toFixed(2)}`;
// Multi-line strings — just press Enter!
const email = `Dear ${caller},
Your incident ${incNum} has been resolved.
Thank you,
IT Support`;Methods are actions you can perform on a string. Think of them as tools: .trim() is like scissors (cuts whitespace), .includes() is like a search function.
| Method | What It Does | Example | Result |
|---|---|---|---|
.length | How many characters | "hello".length | 5 |
.toUpperCase() | ALL CAPS | "hello".toUpperCase() | "HELLO" |
.toLowerCase() | all lowercase | "HELLO".toLowerCase() | "hello" |
.trim() | Remove extra spaces from edges | " hi ".trim() | "hi" |
.includes() | Does it contain this text? | "hello".includes("ell") | true |
.startsWith() | Does it begin with this? | "INC001".startsWith("INC") | true |
.indexOf() | Where does this text appear? | "hello".indexOf("l") | 2 |
.slice(start, end) | Cut out a piece | "hello".slice(0,3) | "hel" |
.replace() | Replace first match | "ha".replace("h","b") | "ba" |
.split() | String → Array | "a,b,c".split(",") | ["a","b","c"] |
Reverse a string: "hello".split('').reverse().join('') → "olleh"
Capitalize first letter: "hello"[0].toUpperCase() + "hello".slice(1) → "Hello"
Quick number to string: 42 + "" → "42"
Quick string to number: +"42" → 42
An array is a list of items, in order, wrapped in square brackets. Think of it like a numbered list: item 0, item 1, item 2... (yes, we start counting at 0, not 1). In ServiceNow, you'll work with arrays when handling multiple records, lists of approvers, CI relationships, etc.
// Create an array
const fruits = ["apple", "banana", "cherry"];
// Access items by position (starts at 0!)
fruits[0] // "apple" ← first item
fruits[1] // "banana" ← second item
fruits[2] // "cherry" ← third item
// Last item? Use .at(-1)
fruits.at(-1) // "cherry"
// How many items?
fruits.length // 3const list = ["A", "B"];
// Add to END
list.push("C"); // ["A","B","C"]
// Add to START
list.unshift("Z"); // ["Z","A","B","C"]
// Remove from END
list.pop(); // removes "C", returns it
// Remove from START
list.shift(); // removes "Z", returns it
// Remove at specific position: splice(index, howMany)
list.splice(1, 1); // removes 1 item at position 1These three methods are the most important array methods. They replace most loops you'd write. Think of them this way:
filter = "Keep only the items that match" (like a coffee filter keeps liquid, removes grounds)
map = "Transform each item" (like a map translates one language to another)
reduce = "Combine all items into one" (like reducing sauce — many ingredients become one)
const incidents = [
{ number: 'INC001', priority: 1, state: 'New' },
{ number: 'INC002', priority: 3, state: 'In Progress' },
{ number: 'INC003', priority: 1, state: 'New' },
{ number: 'INC004', priority: 2, state: 'Resolved' },
];
// FILTER: Keep only priority 1 incidents
const critical = incidents.filter(inc => inc.priority === 1);
// Result: [{number:'INC001',...}, {number:'INC003',...}]
// MAP: Extract just the incident numbers
const numbers = incidents.map(inc => inc.number);
// Result: ['INC001','INC002','INC003','INC004']
// REDUCE: Count by priority
const counts = incidents.reduce((acc, inc) => {
acc[inc.priority] = (acc[inc.priority] || 0) + 1;
return acc;
}, {});
// Result: {1: 2, 2: 1, 3: 1}
// CHAIN THEM: Get numbers of critical new incidents
const result = incidents
.filter(inc => inc.priority === 1 && inc.state === 'New')
.map(inc => inc.number);
// Result: ['INC001', 'INC003']By default, .sort() converts everything to strings and sorts alphabetically. That means [10, 2, 1] becomes [1, 10, 2] — because "10" comes before "2" alphabetically! Always provide a compare function for numbers.
[10, 9, 8, 100].sort(); // [10, 100, 8, 9] — WRONG!
[10, 9, 8, 100].sort((a,b) => a-b); // [8, 9, 10, 100] — Correct ✅
// Sort objects by a property:
incidents.sort((a, b) => a.priority - b.priority);Remove duplicates: [...new Set([1,2,2,3])] → [1,2,3]
Check if array has item: [1,2,3].includes(2) → true
Combine arrays: [...arr1, ...arr2]
Remove falsy values: [0, "hi", null, "", 5].filter(Boolean) → ["hi", 5]
An object is like a contact card: it has labeled fields (keys) and their values. {name: "John", role: "Admin"}. In ServiceNow, every GlideRecord is essentially an object with field names as keys and field values as values. Understanding objects is essential for ServiceNow development.
// Create an object — like filling out a form
const user = {
name: "John",
role: "Admin",
active: true,
greet() { // You can put functions inside objects!
return `Hi, I'm ${this.name}`; // 'this' refers to the object itself
}
};
// Access a value with dot notation
user.name // "John"
user.role // "Admin"
// Access a value with bracket notation (useful for dynamic keys)
user["name"] // "John"
const key = "role";
user[key] // "Admin"
// Change a value
user.name = "Jane";
// Add a new field
user.email = "jane@company.com";
// Call the method inside
user.greet(); // "Hi, I'm Jane"Instead of writing const name = user.name; const role = user.role; over and over, you can "unpack" multiple values in one line.
// Old way — repetitive
const name = user.name;
const role = user.role;
// Destructuring — clean and fast
const { name, role } = user; // Creates two variables at once!
// Rename while destructuring
const { name: userName } = user; // userName = "Jane"
// With default value
const { department = "IT" } = user; // "IT" if department doesn't exist
// Spread operator — copy or merge objects
const copy = { ...user }; // Shallow copy
const updated = { ...user, role: "Manager" }; // Copy + override| Method | What It Does | Example |
|---|---|---|
Object.keys(obj) | Get all key names as an array | ["name","role"] |
Object.values(obj) | Get all values as an array | ["Jane","Admin"] |
Object.entries(obj) | Get [key, value] pairs | [["name","Jane"],...] |
JSON.stringify(obj) | Convert object to JSON string | For logging or sending data |
JSON.parse(str) | Convert JSON string to object | For receiving data |
A function is a recipe. You write the steps once, then use (call) it whenever you need it. Instead of copy-pasting the same code everywhere, you put it in a function and call it by name.
// 1. Function Declaration — can be called before it's defined (hoisted)
function greet(name) {
return `Hello, ${name}!`;
}
greet("John"); // "Hello, John!"
// 2. Function Expression — stored in a variable, NOT hoisted
const greet = function(name) {
return `Hello, ${name}!`;
};
// 3. Arrow Function — shortest syntax, no own 'this'
const greet = (name) => `Hello, ${name}!`;
// Arrow with multiple lines needs curly braces + return
const add = (a, b) => {
const sum = a + b;
return sum; // Must use 'return' with curly braces
};
// Arrow returning an object — wrap in parentheses!
const makeUser = (name) => ({ name, active: true });The biggest difference: arrow functions do NOT have their own this. They borrow this from the surrounding code. Regular functions create their own this. Rule of thumb: Use arrow functions for callbacks. Use regular functions for object methods.
Arrow in Object Method — Broken
const obj = {
name: "John",
greet: () => {
return this.name; // 'this' is NOT obj!
}
};
obj.greet(); // undefined ❌Regular Function in Object — Works
const obj = {
name: "John",
greet() {
return this.name; // 'this' = obj ✅
}
};
obj.greet(); // "John" ✅A closure is a function that "remembers" the variables from the place where it was created, even after that place has finished running. Think of it like a person who remembers their hometown — even after they move away, they still carry those memories.
function createCounter() {
let count = 0; // This variable is "private"
return { // Return an object with functions
increment() { return ++count; }, // Can still see 'count'
decrement() { return --count; },
getCount() { return count; }
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.getCount(); // 2
// Nobody can directly access or change 'count' — it's protected!
// ServiceNow uses this pattern in Script IncludesWhen JavaScript checks an if condition, it converts the value to true/false. Only these 7 values count as false — everything else is true. This is the #1 source of subtle bugs.
// The ONLY 7 falsy values:
false // obvious
0 // zero is falsy
-0 // negative zero is falsy
"" // empty string is falsy
null // null is falsy
undefined // undefined is falsy
NaN // Not-a-Number is falsy
// EVERYTHING ELSE is truthy! Even:
"0" // truthy — it's a non-empty string!
"false" // truthy — it's a non-empty string!
[] // truthy — even an EMPTY array!
{} // truthy — even an EMPTY object!When you need to compare one value against many possibilities, switch is cleaner than a long chain of if/else if. Don't forget break — without it, the code "falls through" and runs the next case too!
const state = current.getValue('state');
switch (state) {
case '1': // New
gs.addInfoMessage('New incident created');
break; // ← Don't forget this!
case '2': // In Progress
gs.addInfoMessage('Work in progress');
break;
case '6': // Resolved
notifyCaller();
break;
default: // If nothing matches
gs.log('Unknown state: ' + state);
}Loops let you do the same thing to every item in a collection. In ServiceNow, you'll use loops constantly — to process every record from a GlideRecord query, every item in an array, etc.
| Loop | When to Use It | Example |
|---|---|---|
for...of | Loop through array values (most common) | for (const item of arr) |
for...in | Loop through object keys | for (const key in obj) |
for (let i=0; ...) | When you need the index number | for (let i=0; i<arr.length; i++) |
.forEach() | Simple array iteration (can't break out early) | arr.forEach(item => ...) |
while | When you don't know how many times to loop | GlideRecord's while(gr.next()) |
// for...of — the one you'll use most for arrays
const incs = ['INC001', 'INC002', 'INC003'];
for (const inc of incs) {
gs.print(inc); // INC001, then INC002, then INC003
}
// for...in — for objects
const config = { host: 'localhost', port: 443 };
for (const key in config) {
gs.print(key + ' = ' + config[key]); // host = localhost, port = 443
}
// while — THE ServiceNow GlideRecord pattern
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.query();
while (gr.next()) { // For each record found...
gs.print(gr.getValue('number'));
}
// break — exit the loop immediately
// continue — skip this iteration, move to nextES6 (released in 2015) added many shortcuts that make JavaScript easier to write and read. Most of these work in ServiceNow (especially on newer versions). Here are the ones you should know.
// 1. const/let — already covered, but use const by default
// 2. Template literals — already covered. Use backticks + ${}
// 3. Arrow functions
const double = n => n * 2; // One-liner
const add = (a, b) => a + b; // Multiple params
// 4. Destructuring
const { name, role } = user; // Object destructuring
const [first, second] = [10, 20]; // Array destructuring
// 5. Spread operator
const newArr = [...oldArr, newItem]; // Copy + add
const newObj = { ...oldObj, key: 'val' }; // Copy + override
// 6. Default parameters
function search(table = 'incident') { ... } // Default if not provided
// 7. Optional chaining — safe property access (Tokyo+)
const mgr = current?.assignment_group?.manager?.name; // No error if any part is null
// 8. Nullish coalescing — better default values (Tokyo+)
const desc = shortDesc ?? 'No description'; // Only triggers for null/undefined, not ""
// 9. Classes
class IncidentHelper {
constructor(gr) { this.gr = gr; }
isCritical() { return this.gr.priority === 1; }
}Older SN instances run on the Rhino engine, which doesn't support ?? and ?.. If you're on Tokyo or later, you're fine. For older instances, stick to || for defaults and manual if checks for safe property access.
When something goes wrong in your code, JavaScript normally stops and throws an error. try/catch lets you catch that error and handle it gracefully instead of crashing. Think of it like a safety net under a trapeze artist.
try {
// Code that might fail
const result = riskyOperation();
} catch (error) {
// If anything fails, this runs instead of crashing
// error.message — the error description
// error.name — the error type
gs.logError('Operation failed: ' + error.message, 'MyScript');
} finally {
// This ALWAYS runs — whether success or failure
// Use for cleanup: closing connections, etc.
cleanup();
}
// You can also THROW your own errors
function validatePriority(priority) {
if (priority < 1 || priority > 5) {
throw new Error(`Invalid priority: ${priority}. Must be 1-5.`);
}
return true;
}gs.log(message, source) → General log
gs.info(message) → Info level
gs.warn(message) → Warning level
gs.error(message) → Error level
Find your logs: System Logs → System Log → All
Regular expressions (regex) are like search patterns on steroids. You use them to check if text matches a pattern (like "is this a valid email?") or to find/replace parts of text.
| Pattern | What It Matches | Use Case |
|---|---|---|
/^INC\d{7}$/ | INC + exactly 7 digits | Validate incident number |
/^\S+@\S+\.\S+$/ | Basic email format | Email validation |
/^\d+$/ | Only digits | Phone/ID validation |
/^[a-f0-9]{32}$/ | 32 hex characters | sys_id format |
// test() — Does the string match? Returns true/false
const isEmail = /^\S+@\S+\.\S+$/.test('user@example.com'); // true
const isINC = /^INC\d{7}$/.test('INC0012345'); // true
// match() — Find all matches
'INC001 and INC002'.match(/INC\d{3}/g); // ['INC001','INC002']
// replace() — Swap matched text
'Hello World'.replace(/\s+/g, ' '); // 'Hello World' (remove extra spaces)ServiceNow uses JavaScript on two sides: the server (where data lives and business rules run) and the client/browser (where users interact with forms). Different scripts run in different places, and you have access to different APIs depending on where you are.
| Runs On | Script Types | Can Use DOM? | Can Use GlideRecord? | In Plain English |
|---|---|---|---|---|
| Server | Business Rules, Script Includes, Workflows | No | Yes | Works directly with the database |
| Client/Browser | Client Scripts, UI Policies, UI Pages | Yes | No (use GlideAjax) | Works with the form the user sees |
Server-side = the kitchen. It has access to the ingredients (database), processes orders (business rules), and sends food out. It never sees the customer directly.
Client-side = the waiter and menu. It interacts with the customer (user), shows options (form), and takes requests. It can't cook (no GlideRecord) — it has to ask the kitchen (GlideAjax).
GlideRecord is how you work with the database in ServiceNow. It's like a cursor that moves through records one at a time. You'll use this every single day as a ServiceNow developer. Let's learn it step by step.
Querying means "find me records that match these conditions." Think of it like searching a filing cabinet.
// Step 1: Create a GlideRecord object for the 'incident' table
var gr = new GlideRecord('incident');
// Step 2: Add filters (like a WHERE clause in SQL)
gr.addQuery('active', true); // Active incidents only
gr.addQuery('priority', 'IN', '1,2'); // Priority 1 or 2
gr.addNullQuery('assigned_to'); // Not yet assigned
// Step 3: Sort the results
gr.orderBy('priority'); // Ascending
gr.orderByDesc('sys_created_on'); // Newest first
// Step 4: Always set a limit! (Safety first)
gr.setLimit(100);
// Step 5: Execute the query!
gr.query();
// Step 6: Loop through results
while (gr.next()) {
gs.print(gr.getValue('number')); // Raw value from DB
gs.print(gr.getDisplayValue('priority')); // Human-readable: "High"
gs.print(gr.number); // Dot notation (read only)
}var gr = new GlideRecord('incident');
if (gr.get('sys_id_here')) { // Returns true if record found
gs.print(gr.getValue('number'));
} else {
gs.log('Record not found!');
}var gr = new GlideRecord('incident');
gr.initialize(); // ALWAYS call this first for new records!
gr.setValue('short_description', 'New issue');
gr.setValue('caller_id', gs.getUserID()); // Current user
gr.setValue('priority', '3');
var sysId = gr.insert(); // Returns the new sys_id
gs.log('Created: ' + sysId);var gr = new GlideRecord('incident');
if (gr.get('sys_id_here')) {
gr.setValue('state', '6'); // Resolved
gr.setValue('close_notes', 'Fixed the issue');
gr.update(); // Save the changes!
}var gr = new GlideRecord('incident');
if (gr.get('sys_id_here')) {
gr.deleteRecord();
}| Method | What It Does | When to Use |
|---|---|---|
gr.getValue(field) | Get the raw database value | Always, when reading a field |
gr.getDisplayValue(field) | Get the human-readable value | For choice fields: "High" instead of "1" |
gr.setValue(field, value) | Set a field value | When modifying a record |
gr.changes(field) | Did this field change? | In Before Business Rules |
gr.changesTo(field, val) | Did it change TO this value? | Trigger action on specific change |
gr.isNewRecord() | Is this a brand new record? | Set defaults only on creation |
gr.setAbortAction(true) | Block the save/insert/delete | Validation failures |
gr.getUniqueValue() | Get the sys_id | Referencing this record |
gr.setLimit(n) | Limit how many records | ALWAYS use this! |
// GlideSystem (gs) — System utilities
gs.getUserID(); // Current user's sys_id
gs.getUserName(); // Current user's username
gs.hasRole('admin'); // Does current user have this role?
gs.addInfoMessage('msg'); // Green banner on form
gs.addErrorMessage('msg'); // Red banner on form
gs.nowDateTime(); // Current date/time
gs.daysAgo(7); // Date 7 days ago
// GlideAggregate — Count, Sum, Average
var ga = new GlideAggregate('incident');
ga.addAggregate('COUNT');
ga.groupBy('priority');
ga.query();
while (ga.next()) {
gs.print(ga.priority + ': ' + ga.getAggregate('COUNT'));
}Business Rules run automatically when a record is displayed, created, updated, or deleted. There are three types, and knowing which to use is critical.
| Type | When It Runs | Best For | Can Change Values? | Needs .update()? |
|---|---|---|---|---|
| Before | Before saving to DB | Setting defaults, validation | Yes | No — auto-saved |
| After | After saving to DB | Notifications, creating related records | Yes (but risky) | Yes |
| Display | Before showing the form | Form messages, setting g_form values | Only display values | N/A |
(function() {
// Set default priority on new records
if (current.isNewRecord()) {
current.setValue('priority', '3');
}
// Validate description length
if (current.getValue('short_description').length > 200) {
gs.addErrorMessage('Description is too long!');
current.setAbortAction(true); // Block the save!
}
})();(function() {
// When state changes to Resolved, create a follow-up task
if (current.changes('state') && current.getValue('state') === '6') {
var task = new GlideRecord('sc_task');
task.initialize();
task.setValue('parent', current.getUniqueValue());
task.setValue('short_description', 'Follow-up: ' + current.getValue('number'));
task.insert();
}
})();function onChange(control, oldValue, newValue, isLoading, isTemplate) {
// ALWAYS check isLoading first!
// Without this, your script runs on form load for every field
if (isLoading) return;
// When priority changes to 1, show and require the reason field
if (newValue === '1') {
g_form.setVisible('reason', true);
g_form.setMandatory('reason', true);
} else {
g_form.setVisible('reason', false);
g_form.setMandatory('reason', false);
}
}A Script Include is like a library of functions that other scripts can call. Think of it as a toolbox — you put your commonly-used functions in one place and reuse them everywhere. It's also how GlideAjax works (client calls server).
var IncidentUtils = Class.create();
IncidentUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {
// Count critical active incidents
getCriticalCount: function() {
var ga = new GlideAggregate('incident');
ga.addQuery('active', true);
ga.addQuery('priority', '1');
ga.addAggregate('COUNT');
ga.query();
if (ga.next()) return ga.getAggregate('COUNT');
return 0;
},
type: 'IncidentUtils'
});GlideRecord in a Loop (N+1 Problem)
Running a GlideRecord query inside a while loop fires a SEPARATE database call each time. This destroys performance.
// DON'T DO THIS!
while (inc.next()) {
var user = new GlideRecord('sys_user');
user.get(inc.getValue('assigned_to'));
// A new DB query EVERY loop!
}Use Dot-Walking Instead
Dot-walking follows references in a single query. Let ServiceNow handle the join — it's much faster.
// DO THIS instead!
while (inc.next()) {
var mgrName = inc.assigned_to.manager.name;
// One query, join handled by SN!
}current.update() in After Business Rule
Calling current.update() in an After BR triggers Business Rules again — potentially causing an infinite loop!
// After Business Rule — DANGEROUS
current.setValue('work_notes', 'Updated');
current.update(); // Triggers BRs again! 💥Set Values in Before BR (No .update Needed)
In a Before BR, just set the value. It saves automatically when the record saves.
// Before Business Rule — CORRECT
current.setValue('work_notes', 'Updated');
// No .update() needed! Saved automatically ✅Missing setLimit on Queries
Without setLimit(), GlideRecord returns ALL matching records. On large tables, this can bring down the instance.
var gr = new GlideRecord('syslog');
gr.query(); // MILLIONS of rows! 💥Always Set a Limit
Add setLimit() to every query unless you have a specific reason not to.
var gr = new GlideRecord('syslog');
gr.setLimit(100); // Safe! ✅
gr.query();- Forgetting
isLoadingcheck in Client Scripts → script runs on every field on form load - Using
current.update()in After BR → recursive triggers / infinite loops - Missing
setLimiton GlideRecord → performance disaster - Using
==instead of===→ type coercion bugs - GlideRecord inside a loop → N+1 query problem, slow performance
| String → Number | +"42" or parseInt("42", 10) |
| Number → String | 42 + "" or String(42) |
| Any → Boolean | !!value |
| Array → String | arr.join(",") |
| String → Array | str.split(",") |
| Object → JSON | JSON.stringify(obj) |
| JSON → Object | JSON.parse(str) |
val || default | Use default if val is falsy |
val ?? default | Use default only if null/undefined |
cond && action() | Do action only if true |
obj?.prop?.method() | Safe chain — no error if null |
| Unique values | [...new Set(arr)] |
| Sum | arr.reduce((a,b) => a+b, 0) |
| Max | Math.max(...arr) |
| Last item | arr.at(-1) |
| Remove falsy | arr.filter(Boolean) |
| Flatten | arr.flat() |
| Query | gr.addQuery(); gr.query(); |
| Get one | gr.get('sys_id') |
| Insert | gr.initialize(); gr.setValue(); gr.insert(); |
| Update | gr.setValue(); gr.update(); |
| Delete | gr.deleteRecord(); |
| Block save | current.setAbortAction(true); |
| Field changed? | current.changes('field') |
false 0 -0 "" null undefined NaN
// Everything else is truthy, including: "0", "false", [], {}, function(){}typeof null return in JavaScript?[1,2,3].reduce((a,b) => a+b, 0) return?var gr = new GlideRecord('incident');
// ???
gr.setValue('short_description', 'Test');
gr.insert();isLoading in a Client Script onChange function?