Week 6: Script Includes: Reusable Server-Side Logic

A Script Include is the closest thing on this platform to an Angular service: a class, defined once, instantiated on demand, holding logic other scripts inject and call instead of re-implementing. Getting used to this pattern now pays off directly in Week 8, when you call one from the browser for the first time.

Module 6 of 25 Week 6 of 26 ~3–4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Write a class-based Script Include using ServiceNow's prototype pattern
  • Explain the difference between a server-only and a client-callable Script Include
  • Structure a client-callable Script Include correctly with AbstractAjaxProcessor

1. The Class-Based Script Include Pattern

ServiceNow's server-side JavaScript predates ES6 classes, so Script Includes use a prototype-based pattern instead — but the shape is exactly what you'd expect from any other object-oriented service: a constructor-style function, methods on its prototype, and a type string matching the record's name.

Script Include — IncidentUtils
var IncidentUtils = Class.create();
IncidentUtils.prototype = {
  initialize: function() {},

  countOpenByGroup: function(groupSysId) {
    var ga = new GlideAggregate('incident');
    ga.addQuery('assignment_group', groupSysId);
    ga.addActiveQuery();
    ga.addAggregate('COUNT');
    ga.query();
    return ga.next() ? ga.getAggregate('COUNT') : 0;
  },

  type: 'IncidentUtils'
};

Every field in the object literal after initialize becomes a method callable on an instance. This is what a Business Rule or another Script Include calls into — never write GlideRecord logic directly in three different Business Rules when it can live once, here, and get called from all three.

2. Calling a Script Include

from a Business Rule (server-side)
var utils = new IncidentUtils();
var count = utils.countOpenByGroup(current.assignment_group);

if (count > 20) {
  current.priority = 1;
}

Script Includes load lazily by name — you don't need an import or a manual reference; referencing the class name anywhere in server-side script is enough for the platform to locate and load the matching Script Include record.

3. Client-Callable Script Includes

By default, a Script Include is server-only — it can't be invoked from browser-side JavaScript at all, which is the safe default (server logic shouldn't be casually exposed to the client). To allow a client script to call in via GlideAjax (Week 8), two things must both be true: the Script Include's Client callable checkbox is checked, and it extends AbstractAjaxProcessor.

Script Include — client-callable — set 'Client callable' checkbox on the record
var IncidentAjax = Class.create();
IncidentAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {

  getOpenCountForGroup: function() {
    var groupSysId = this.getParameter('sysparm_group');
    var utils = new IncidentUtils();
    return utils.countOpenByGroup(groupSysId);
  },

  type: 'IncidentAjax'
});

this.getParameter(name) reads a value the client sent via GlideAjax.addParam() — you'll use this pairing directly next week once ACLs are in place to control who's even allowed to call it, and the week after that when you wire up the browser side.

Client-callable is a bigger surface area than it looks

Every method on a client-callable Script Include is reachable from any authenticated (or even anonymous, depending on ACLs) browser session — treat it like a small API endpoint, not an internal implementation detail, and validate parameters defensively inside it.

4. Hands-on Exercise

Hands-on

Extract duplicated logic into a Script Include

Requirements:

  1. Create IncidentUtils as shown, plus a second method isOverdue(incidentSysId) that returns true if a P1/P2 incident has been open more than 4 hours.
  2. Call isOverdue() from a Business Rule that sets a custom u_escalation_flag boolean field when true.
  3. Create IncidentAjax extending AbstractAjaxProcessor, with a method exposing countOpenByGroup to the client (don't wire up the browser call yet — that's Week 8).
  4. Confirm in the Script Include's record that "Client callable" is checked only on IncidentAjax, not on IncidentUtils.

5. Knowledge Check

Four quick questions. Expand each to check your answer.

Q1

What's the Angular-service analogy for a Script Include, and where does it break down?

Both are reusable, injectable-feeling units of logic that other code calls into rather than duplicating. It breaks down in mechanics: Script Includes use ES5 prototype-based classes (Class.create()) rather than TypeScript classes with decorators, and there's no dependency-injection container — you reference the class name directly and the platform lazily locates the matching record.

Q2

Why is a Script Include server-only by default?

Exposing server-side logic to the browser is a deliberate security decision, not a default — most server logic (data access, business logic) has no reason to be callable from arbitrary client-side code. Requiring both the "Client callable" checkbox and extending AbstractAjaxProcessor makes client-exposure an explicit, visible opt-in rather than something that happens by accident.

Q3

What two things must be true for a Script Include to be callable via GlideAjax from the browser?

Its "Client callable" checkbox must be checked, and its prototype must extend AbstractAjaxProcessor (via Object.extendsObject). Missing either one means a GlideAjax call to it will fail even if the method itself is written correctly.

Q4

Why should a client-callable Script Include validate its parameters defensively?

Every method on it is reachable from any browser session that can reach the instance — effectively a small API surface, not a private implementation detail. Parameters arriving via getParameter() come from client-side script that a user could tamper with, so the same defensive validation you'd apply to any externally-reachable endpoint applies here.