_
_
Back to Blog

PSA: Composable, Functional Script Includes in ServiceNow

Reduce overhead and maintenance burden in your ServiceNow JavaScript
5
min read
|
by
Andy Halstead
&
Zayn Moselhy
April 15, 2026

ServiceNow provides the ability to create server-side libraries of JavaScript called Script Includes. This is the recommended way to add larger segments of JavaScript code to your otherwise "no-code" or "low-code" workflows and callbacks. I'm going to discuss a little bit about how ServiceNow scripting works, how it is different from a more "standard" JavaScript environment, and then we will get to a method of organizing your Script Includes to minimize unnecessary overhead and speed up your instance. I'm assuming you know a bit about JavaScript and how to create a Script Include.

JavaScript in ServiceNow

Primarily a Java web application, ServiceNow requires a scripting language to allow for customers (and their own development teams) to fully customize and architect over the rest of the platform. To make Java classes (and by extension, the database) available to the scripting environment, the development team uses the Rhino library. This is a Java-based JavaScript interpreter that allows for JavaScript contexts to be created on the fly. Data can pass through back and forth between database, Java, and the JavaScript context with this setup.

There are tradeoffs for the convenience in integration on the Java end. Rhino is a from-scratch implementation in Java, and to limit its complexity, it does not have the same kind of language feature coverage that you would see in e.g. Node or the browser. For a long time, the JavaScript language support in ServiceNow was stuck at ES5 (aka ECMAScript 2009). Rhino mostly implements ES5. Recently, ServiceNow has updated the Rhino engine dependency in their Java application, so "ES2021 mode" has appeared as an option for script execution. While this opens up a lot of useful syntax like arrow functions and the null-ish coalescing operator, again, a large portion of ES2021 is unimplemented by Rhino.

One feature they left out specifically relates to our topic--the class keyword. Classes in JavaScript are a late addition (and not supported by Rhino’s interpreter, even in ES2021 mode). The traditional alternative, Objects, are small dictionary data structures integral to JavaScript, but they are not classes in the Gang-of-Four OOP sense. Technically, you can emulate some features of OOP in the JavaScript object prototype chain, but the rules for inheritance and hiding are complicated and differ between implementations, so this pattern is usually avoided. However, in order to bridge this gap, and allow for easy inter-operability with proper Java classes in their application, ServiceNow via Rhino provides a Class object to the JavaScript runtime contexts. This is what you see by default when you create a new Script Include: var MyScriptInclude = Class.create(...);--because this is backed by a “real” Java class, it allows for a new, unique flavor of OOP practices specific to ServiceNow Script Includes (that I will not explain how to utilize here).

"Functional Programming" in JavaScript

In my humble opinion, OOP is not where JavaScript shines, and I am not a huge fan of the inheritance pattern. You may have run into Script Includes that inherit from other Script Includes. Often this is to "customize" or extend an existing off-the-shelf ServiceNow Script Include without modifying the base record to keep upgrades manageable, which is a fine use case. However, they can also be (ab)used to create large graph-like chains of inheritance between Script Includes.

Because JavaScript is minimally and dynamically typed, managing function overrides, the "shape" of returned values, and inherited methods is a huge chore and creates lots of testing and error handling overhead. It also forces developers to constantly bounce back and forth between as many Script Includes as there are in an inheritance chain, and often changes to deeply nested Includes make it so that the entire inheritance chain must be re-tested.

In JavaScript, Objects are first class, but so are functions. Technically, all functions are Objects of the Function type. This means we can do a lot with "bare" functions that we cannot do in OOP-focused languages like Java, which require functions (methods) to be members of class objects. "Functional" programming is a style of organization of your code that somewhat counters OOP--small composable functions, that are simple to reason about individually, are built up and combined into powerful abstractions. 

Simple Script Include Example

Here is a typical Script Include, written using the default template provided when you enter a name into the Script Include form. We are using ES2021 mode for our includes to get access to arrow functions, but these same principals apply to "compatibility" ES5 mode as well. Our script simply calculates the hypotenuse of a triangle using a sum of squares function that operates on an array of numbers. What the functions do is not important–we’re looking at how they’re defined.


  var TestClassInclude = Class.create();
TestClassInclude.prototype = {
    initialize: function() {
		// .. state ...
	},
	sumOfSquares: function (numbers) {
		return numbers.reduce((sum, n) => sum + (n * n));
	},
	hypotenuse: function (side1, side2) {
		return Math.sqrt(this.sumOfSquares([side1, side2]));
	},
    type: 'TestClassInclude' 
};

As mentioned before, this uses the Class.create() function to create a special object, and then we add our methods to the prototype of the object that is returned. The way ServiceNow loads this is simple--because there are no imports implemented in Rhino, Script Includes are discretely processed and mapped to the context, but at the code level. What this means is that we don't actually have to use the Class.create() method if we don't need state on our Script Include.

“State” here refers to the code in the initialize function, where you can set variables that will be available and persist between method calls on the same instantiated class object. This can be useful, but usually, state is a crutch that makes maintenance and debugging more painful than it needs to be. It should only be used in a last-resort situation where data must be stored internally to be used between different method calls in a specific way.

The only other reason to use Class.create would be in a situation where you need to get something from JavaScript in the Java code--an application that is only useful to the developers of ServiceNow working in Java on the web application itself.

You would call this include by writing: new TestClassInclude().hypotenuse(a, b); -- note the new keyword. This runs the object instantiation and your initialize method.

Functional Example

In our hypotenuse example, there is no state required. We get our arguments as parameters and all we need to do is return a result. This is a key tenet of functional design, where we limit our current concerns to the inputs and outputs of a single function. 

As long as we can keep inputs and outputs consistent, the function becomes a black box we can utilize in creating new, more complicated functions. It is possible to utilize “state” between function executions the way we do in the Class Script Include, but it requires that state be added into the input/output of the functions in question instead.

We’re going to erase the template, but don’t worry, this will still work. Instead of Class.create(), lets just use "bare" JavaScript Object:


var TestFunctionInclude = {};

TestFunctionInclude.sumOfSquares = function (numbers) {
	return numbers.reduce((sum, n) => sum + (n * n));
};

TestFunctionInclude.hypotenuse = function(side1, side2) {
	return Math.sqrt(this.sumOfSquares([side1, side2]));
};

Here, we have created an empty Object using an object literal with nothing inside (the {}). Instead of messing with the prototype system, we can simply assign our functions as properties on the object. This is basically just to namespace the functions and group them together inside the Script Include.

You can use this Script Include by writing: TestFunctionInclude.hypotenuse(a, b); -- note the lack of a new keyword.

Basic Benchmark

Now we are going to write some test code to show how the Class.create() / new ... system of setting up Script Includes adds overhead. It is not an astounding amount of extra processing time. But often in ServiceNow, JavaScript snippets can be called many times in a row. Imagine an import with 100,000 rows where some column must be modified or used in a calculation before it can be written to the database. A few milliseconds of overhead can quickly add up to minutes or hours in such cases.


// run our function this many times
var n = 100000;
// shorthand to get a unix timestamp for a stopwatch
var now = function () { return (new GlideDateTime()).getNumericValue(); };

// call a function with two random numbers n times
// and time how long it takes in milliseconds
var test = function(someFunction) {
	var timeStart = now();
	for(var i = 0; i < n; i++) {
        // here we call the function supplied
        // in the parameter. we don't use the
        // return value but we could
		var _ = someFunction(
			Math.random() * 10,
			Math.random() * 20
		);
	}
	var timeEnd = now();
    // return the stopwatch duration of the loop
	return timeEnd - timeStart;
};

var includeDuration = test(function(a, b) {
	// using the Class.create() include
	new TestClassInclude().hypotenuse(a, b);
});

var functionDuration = test(function(a, b) {
	// using the functional include
	TestFunctionInclude.hypotenuse(a, b);
});

gs.info("Class: " + includeDuration);
gs.info("Function: " + functionDuration);

In this (quite contrived) example, we implement a test function that calls our Script Include functionality 100,000 times in a row with random numbers. It takes note of the time taken to perform this action. The test function also illustrates how functions in JavaScript are first class--the someFunction parameter is called with random arguments, and we can provide our two different test Script Include calls as arguments later without having to rewrite our loop.

On my personal (Zurich at time of writing) development ServiceNow instance it only took a few seconds, but be careful running long loops like this in Background Scripts, as it can lock up your session until it is finished. The results shouldn't be surprising:

*** Script: Class: 1524

*** Script: Function: 944

Again, we're doing simple math here, and this example where we calculate 100,000 hypotenuses is not exactly something you'd often do in the real world, but the overhead of Class is quite measurable. Our functional Script Include completed the test run half a second faster. When you scale this out to even larger imports, multiple columns, and running repeatedly on a schedule, you can end up burning quite a lot of performance bandwidth on your instance for no real benefit. Instead, functional includes have some real benefits:

  • No overhead from Java interop
  • Forces stateless design
  • Calling the functional include is just as simple
  • Only reason about functions, not inheritance chains
  • Updates to a functional include are guaranteed not to affect other functional includes (if you can ensure the consistency of inputs and outputs)
  • Unit tests are shorter and become more relevant/enforceable

For your health and sanity, and that of every fellow ServiceNow developer that will eventually try to read code that you write, for your next Script Include, I hope you give functional design a try! If you're rethinking how you structure ServiceNow code or looking to improve performance at scale, reach out to RapDev to see how we can help optimize your instance.

Written by
Andy Halstead
Freeland, WA
Andy has been hacking on ServiceNow since 2013. He enjoys cats, electronic music, and dependent type systems.
Written by
Andy Halstead
Freeland, WA
Andy has been hacking on ServiceNow since 2013. He enjoys cats, electronic music, and dependent type systems.
More by
Andy
No items found.
Resources

We don’t believe in hoarding knowledge

We go further and faster when we collaborate. Geek out with our team of engineers on our learnings, insights, and best practices to unlock maximum value and begin your business transformation today.

Blogresources
Explore Resources