📖 Guides
⚙️ Scripts & Business Logic

⚙️ ServiceNow — Scripts & Business Logic

ServiceNow Developer Guide · Digital Kimya


ServiceNow scripting runs in two distinct environments that never share memory directly: the server (Nashorn/Rhino JavaScript engine) and the client (the user's browser). Understanding which side your code runs on — and how to bridge between them — is the most fundamental skill in ServiceNow development.

Diagramme interactif — Server vs Client

🖥️ Serveur (Nashorn/Rhino)
Business Rule
Before · After · Async · Display
appelle
Script Include
Librairie réutilisable serveur
GlideRecord API
Query · Insert · Update · Delete
Scheduled Job
Exécution planifiée (CRON)
Notification / Email Event
Emails automatisés
Inbound Action
Traitement emails entrants
GlideAjax
🌐 Client (navigateur)
Client Script
onLoad · onChange · onSubmit
UI Policy
Mandatory · ReadOnly · Visible
GlideAjax
Appel async client → serveur
UI Action
Bouton / Lien dans le form
UI Macro / UI Page
Composants UI personnalisés
Catalog Client Script
Logique sur les items catalogue
👆 Cliquez sur un bloc pour voir sa définition

The Two Environments

┌─────────────────────────────┐         ┌────────────────────────────────┐
│   SERVER (Nashorn/Rhino)    │         │     CLIENT (Browser)           │
│                             │         │                                │
│  Business Rule              │         │  Client Script                 │
│  Script Include             │◄─GlideAjax─►GlideAjax (caller)          │
│  GlideRecord API            │         │  UI Policy                     │
│  Scheduled Job              │         │  UI Action (client-side)       │
│  Notification / Email Event │         │  UI Macro / UI Page            │
│  Inbound Action             │         │  Catalog Client Script         │
└─────────────────────────────┘         └────────────────────────────────┘

Rule of thumb: if it touches the database, it's server-side. If it changes what the user sees without a page reload, it's client-side.


Server-Side Scripts

Business Rule

A Business Rule (BR) is a server-side script triggered by a database operation on a specific table. It is the most powerful and most abused script type in ServiceNow.

Four trigger timings:

TimingWhen it runsCan modify values?Blocks the user?
BeforeBefore the record is written to DB✅ Yes — changes are saved✅ Yes
AfterAfter the record is written❌ No (must use .update())✅ Yes
AsyncAfter write, in background thread❌ No (must use .update())❌ No
DisplayBefore a form is rendered (read operations)N/A — scratchpad only✅ Yes

Best practice: use async whenever you don't need to block the user's operation. Heavy processing (emails, integrations, complex queries) should always be async.

Example — Before Insert:

// Business Rule: "Auto-set Priority" — Before Insert on incident
// Condition: current.impact == 1 && current.urgency == 1
 
(function executeRule(current, previous) {
    current.priority = 2; // High priority
    current.assignment_group = 'a1b2c3d4...'; // Network Ops sys_id
})(current, previous);

Example — Async After Update:

// Business Rule: "Notify Manager on P1" — Async, After on incident
// Condition: current.priority == 1 && current.priority.changes()
 
(function executeRule(current, previous) {
    var evt = gs.eventQueue('incident.p1.created', current, current.caller_id.manager, gs.getUserName());
})(current, previous);

Script Include

A Script Include is a reusable JavaScript library on the server. Instead of duplicating logic across multiple Business Rules, you centralise it in a Script Include and call it from anywhere server-side.

Key properties:

PropertyValue
Accessible fromBusiness Rules, other Script Includes, REST APIs
Client-accessibleOnly via GlideAjax (must extend AbstractAjaxProcessor)
ScopeGlobal or Scoped App

Example — Utility class:

// Script Include: IncidentUtils
var IncidentUtils = Class.create();
IncidentUtils.prototype = {
    initialize: function() {},
 
    getPriority: function(impact, urgency) {
        // Priority matrix: impact 1-3, urgency 1-3
        var matrix = {
            '1-1': 1, '1-2': 2, '1-3': 3,
            '2-1': 2, '2-2': 3, '2-3': 4,
            '3-1': 3, '3-2': 4, '3-3': 5
        };
        return matrix[impact + '-' + urgency] || 5;
    },
 
    isVIP: function(userId) {
        var user = new GlideRecord('sys_user');
        user.get(userId);
        return user.getValue('vip') === 'true';
    },
 
    type: 'IncidentUtils'
};
 
// Calling it from a Business Rule:
var utils = new IncidentUtils();
var priority = utils.getPriority(current.impact, current.urgency);
current.priority = priority;

GlideRecord API

GlideRecord is the primary database API in ServiceNow. Every read, write, create, and delete operation goes through it server-side.

Reading records:

// Query all incidents in state "New" (1)
var gr = new GlideRecord('incident');
gr.addQuery('state', 1);
gr.addQuery('priority', '<=', 2); // P1 and P2 only
gr.orderByDesc('opened_at');
gr.setLimit(100);
gr.query();
 
while (gr.next()) {
    gs.info('Incident: ' + gr.number + ' — ' + gr.short_description);
}

Creating a record:

var gr = new GlideRecord('incident');
gr.initialize();
gr.short_description = 'Network outage — Building A';
gr.impact = 1;
gr.urgency = 1;
gr.caller_id = gs.getUserID();
var sysId = gr.insert(); // Returns sys_id of new record
gs.info('Created: ' + sysId);

Updating records:

// Bulk update: close all resolved incidents older than 5 days
var gr = new GlideRecord('incident');
gr.addQuery('state', 6); // Resolved
gr.addQuery('resolved_at', '<', gs.daysAgo(5));
gr.query();
 
while (gr.next()) {
    gr.state = 7; // Closed
    gr.close_notes = 'Auto-closed after 5 days resolved';
    gr.update();
}

Common GlideRecord pitfalls:

MistakeProblemFix
No .query() before .next()No resultsAlways call .query() first
Direct string concatenation in queriesSQL injection riskUse addQuery()
Looping .update() on 10k recordsTimes out, performance issueUse GlideRecord in Background Scripts or Scheduled Jobs
gr.field vs gr.getValue('field').field returns GlideElement objectUse .getValue() for strings

Scheduled Job

A Scheduled Job runs a server-side script on a CRON schedule — no user interaction required.

// Scheduled Job: "Auto-close Resolved Incidents"
// Schedule: Daily at 02:00
 
var gr = new GlideRecord('incident');
gr.addQuery('state', 6); // Resolved
gr.addQuery('resolved_at', '<', gs.daysAgo(5));
gr.query();
 
var count = 0;
while (gr.next()) {
    gr.state = 7; // Closed
    gr.close_notes = 'Auto-closed: resolved for 5+ days';
    gr.update();
    count++;
}
 
gs.log('Auto-close job completed: ' + count + ' incidents closed', 'AutoCloseJob');

Use cases: nightly data cleanup, SLA escalation checks, scheduled reports, integration polling.


Client-Side Scripts

Client Script

Client Scripts run in the user's browser — they fire on form events without a server round-trip.

TriggerWhenUse case
onLoadForm first rendersSet field defaults, hide sections
onChangeA specific field value changesClear dependent fields, show/hide logic
onSubmitUser clicks SubmitValidate before save, confirm dialogs

Example — onChange:

// Client Script: Clear subcategory when category changes
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading) return; // Don't run on initial load
    g_form.clearValue('subcategory');
    g_form.clearOptions('subcategory');
}

Example — onLoad:

// Client Script: Hide resolution fields until state = Resolved
function onLoad() {
    var state = g_form.getValue('state');
    var isResolved = (state == '6');
    g_form.setVisible('resolve_time', isResolved);
    g_form.setVisible('close_notes', isResolved);
}

UI Policy

UI Policy is the no-code alternative to Client Scripts for simple show/hide/mandatory logic. Always prefer UI Policy over Client Script for these cases — it's faster to configure, easier to audit, and doesn't require JavaScript knowledge.

Use caseUI PolicyClient Script
Make field mandatory based on state✅ PreferredOverkill
Hide a field based on category✅ PreferredOverkill
Clear a field when another changes❌ Not supported✅ Required
Complex multi-field conditional logic❌ Limited✅ Required
Fetch data from server❌ Not possible✅ Via GlideAjax

Example — UI Policy:

  • Table: incident
  • Condition: state is Resolved
  • Actions: resolution_notes → Mandatory=true, Visible=true

GlideAjax — The Client-Server Bridge

GlideAjax is how client-side code calls server-side logic without a page reload. The client instantiates a GlideAjax object pointing to a Script Include, calls a method, and receives the response in an async callback.

Server-side (Script Include — must extend AbstractAjaxProcessor):

// Script Include: LocationLookupAjax
var LocationLookupAjax = Class.create();
LocationLookupAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {
    getBuilding: function() {
        var userId = this.getParameter('sysparm_user_id');
        var user = new GlideRecord('sys_user');
        user.get(userId);
        return user.getValue('building');
    },
    type: 'LocationLookupAjax'
});

Client-side (Client Script):

// Client Script: Auto-fill location from caller
function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || !newValue) return;
 
    var ga = new GlideAjax('LocationLookupAjax');
    ga.addParam('sysparm_name', 'getBuilding');
    ga.addParam('sysparm_user_id', newValue);
    ga.getXMLAnswer(function(answer) {
        g_form.setValue('location', answer);
    });
}

Why GlideAjax matters: direct GlideRecord calls are not available in Client Scripts — the browser has no access to the database. GlideAjax is the only safe, supported bridge.


Implementation Guide

ServiceNow Scripting — Decision Framework

Click any step to expand · 5 steps

1
🎯Identify the trigger

Is this triggered by a DB operation (insert/update/delete/query)? → Server-side. Is it triggered by user interaction on a form? → Client-side. Is it scheduled? → Scheduled Job.

Server vs client decisionTrigger type identifiedTable scope defined
2
🔧Choose the right script type
3
Write with performance in mind
4
🧪Test in DEV — with impersonation
5
📦Capture in Update Set and promote

Script Type Quick Reference

NeedUseSide
Run code when record is savedBusiness Rule (before/after)Server
Run code without blocking userBusiness Rule (async)Server
Reuse logic across multiple BRsScript IncludeServer
Read/write database recordsGlideRecord APIServer
Run code on a scheduleScheduled JobServer
Send emails based on eventsNotification + Email EventServer
React to inbound emailInbound ActionServer
Change form when field changesClient Script (onChange)Client
Validate on submitClient Script (onSubmit)Client
Show/hide/mandatory — simple rulesUI PolicyClient
Call server from clientGlideAjaxBridge
Custom buttons / linksUI ActionBoth

Part of the Digital Kimya (opens in a new tab) ServiceNow Guide Series — Identity & Access · Tables & Data · Deployment

Digital Kimya — MENA & Europe

Ready to implement what you've read?

Our ITSM practitioners deliver ITIL 4 & 5 projects across ServiceNow, Jira SM, SMAX and BMC Helix — from initial assessment to full ESM deployment.

🚀 ITIL Implementation🔧 ITSM Platform Setup📊 Assessment & Roadmap🏭 Industry-Specific Projects
🌍 MENA & Europe🎯 ITIL 4 & 5 Certified🏢 6 Industries covered Assessment in 2 weeks
contact@digitalkimya.net