Script Includes in ServiceNow: The Complete Developer Guide

Script Includes are the foundation of good ServiceNow development. They are reusable server-side JavaScript libraries you can call from Business Rules, Scheduled Jobs, REST API scripts, Flow Designer scripts, and Client Scripts via GlideAjax. If you are duplicating logic across multiple Business Rules or writing long scripts directly in execution contexts, you are missing the most important code organisation tool the platform provides. This guide covers everything: the class structure, static vs instance methods, client-callable Script Includes with AbstractAjaxProcessor, scoped vs global scope, testing patterns, and real production examples.

What is a Script Include?

A Script Include is a named JavaScript object stored in the ServiceNow database that is loaded on demand when referenced by other scripts. Unlike Business Rules, which are tied to a specific table and fire on specific events, Script Includes have no trigger — they exist purely as reusable code that other scripts call explicitly.

They solve one fundamental problem: code duplication. Without Script Includes, developers copy and paste logic across Business Rules, Scheduled Jobs, and REST API scripts. When that logic needs to change — a field name changes, a business rule updates, a new condition is added — every copy needs updating. Script Includes give you one place to maintain shared logic and one place to fix bugs.

Script Includes are accessed at System Definition > Script Includes in the navigator.

The standard Script Include structure

ServiceNow uses a JavaScript prototypal class pattern based on Prototype.js. Every Script Include follows this structure:

var MyScriptInclude = Class.create();
MyScriptInclude.prototype = {
    initialize: function() {
        // Constructor — runs when you instantiate with new MyScriptInclude()
        // Set up instance variables here
        this.tableName = 'incident';
        this.defaultQuery = 'active=true';
    },

    myMethod: function(param1, param2) {
        // Instance method — access instance variables with this.
        var gr = new GlideRecord(this.tableName);
        gr.addEncodedQuery(this.defaultQuery);
        gr.query();
        return gr.getRowCount();
    },

    anotherMethod: function() {
        // Can call other methods on this instance
        return this.myMethod();
    },

    type: 'MyScriptInclude' // Must match the variable name exactly
};

Three things are required:

  • Class.create() creates the class constructor
  • initialize is the constructor function — runs when you call new MyScriptInclude()
  • type must match the variable name exactly — this is used by the platform for debugging and error messages

The Script Include name in the record must also match the variable name. If the record is named MyScriptInclude, the variable must be var MyScriptInclude.

Calling a Script Include from a Business Rule

(function executeRule(current, previous) {

    var utils = new MyScriptInclude();
    var count = utils.myMethod('value1', 'value2');

    if (count > 5) {
        current.setValue('priority', '1');
    }

})(current, previous);

Instantiate with new, then call methods on the instance. This is the most common pattern — a Business Rule that delegates its logic to a Script Include instead of containing it inline.

For guidance on when to use Business Rules vs other execution contexts, see the complete guide to Business Rule types in ServiceNow.

Passing arguments through initialize()

You can make Script Includes configurable by accepting arguments in the constructor:

var IncidentUtils = Class.create();
IncidentUtils.prototype = {
    initialize: function(assignedGroup, priority) {
        this.assignedGroup = assignedGroup;
        this.priority = priority || '3'; // default to P3 if not provided
    },

    getOpenCount: function() {
        var ga = new GlideAggregate('incident');
        ga.addEncodedQuery('active=true^assignment_group=' + this.assignedGroup + '^priority=' + this.priority);
        ga.addAggregate('COUNT');
        ga.query();
        if (ga.next()) {
            return parseInt(ga.getAggregate('COUNT'));
        }
        return 0;
    },

    getOldestOpen: function() {
        var gr = new GlideRecord('incident');
        gr.addEncodedQuery('active=true^assignment_group=' + this.assignedGroup);
        gr.orderBy('opened_at');
        gr.setLimit(1);
        gr.query();
        if (gr.next()) {
            return gr.getValue('opened_at');
        }
        return null;
    },

    type: 'IncidentUtils'
};

// Usage in a Business Rule:
var utils = new IncidentUtils(current.getValue('assignment_group'), '1');
var p1Count = utils.getOpenCount();
gs.log('Open P1s for this group: ' + p1Count);

This pattern makes Script Includes flexible and testable — you can instantiate with different arguments in different contexts.

Static Script Includes

Not every Script Include needs to be a class. If you have utility functions that do not need instance state — they just take inputs and return outputs — a static object is simpler:

var DateUtils = {
    formatGlideDate: function(glideDateTimeStr) {
        if (!glideDateTimeStr) return '';
        return glideDateTimeStr.substring(0, 10);
    },

    isWeekend: function(glideDateTimeStr) {
        var date = new GlideDateTime(glideDateTimeStr);
        var dayOfWeek = date.getDayOfWeekLocalTime();
        return dayOfWeek === 1 || dayOfWeek === 7; // Sunday=1, Saturday=7
    },

    addBusinessDays: function(startDate, days) {
        var dt = new GlideDateTime(startDate);
        var added = 0;
        while (added < days) {
            dt.addDaysLocalTime(1);
            var dow = dt.getDayOfWeekLocalTime();
            if (dow !== 1 && dow !== 7) added++; // skip weekends
        }
        return dt.getValue();
    }
};

// Usage — no new keyword needed
var formatted = DateUtils.formatGlideDate(current.getValue('opened_at'));
var isWeekend = DateUtils.isWeekend(current.getValue('opened_at'));

Static Script Includes work well for pure utility functions. The downside: they cannot be client-callable via GlideAjax (that requires the class pattern with AbstractAjaxProcessor).

Client-callable Script Includes with GlideAjax

One of the most important Script Include patterns is the client-callable Script Include — a server-side library that can be called from client-side scripts (Client Scripts, UI Policies, UI Actions) via asynchronous Ajax requests.

This is the correct way to fetch server-side data from a client script without synchronous GlideRecord queries, which are deprecated. The pattern uses AbstractAjaxProcessor and the GlideAjax API.

Step 1 — Create the server-side Script Include

The Script Include must extend AbstractAjaxProcessor and have the Client callable checkbox checked:

var IncidentAjaxUtils = Class.create();
IncidentAjaxUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {

    getOpenCountForUser: function() {
        // getParameter() reads values sent from the client
        var userId = this.getParameter('sysparm_user_id');

        var ga = new GlideAggregate('incident');
        ga.addEncodedQuery('active=true^caller_id=' + userId);
        ga.addAggregate('COUNT');
        ga.query();

        if (ga.next()) {
            return ga.getAggregate('COUNT');
        }
        return '0';
    },

    getIncidentDetails: function() {
        var sysId = this.getParameter('sysparm_sys_id');
        var result = {};

        var gr = new GlideRecord('incident');
        if (gr.get(sysId)) {
            result.number = gr.getValue('number');
            result.state = gr.getDisplayValue('state');
            result.priority = gr.getDisplayValue('priority');
            result.shortDesc = gr.getValue('short_description');
        }

        // Return JSON string for complex data
        return JSON.stringify(result);
    },

    type: 'IncidentAjaxUtils'
});

Step 2 — Call from a Client Script

function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || !newValue) return;

    var ga = new GlideAjax('IncidentAjaxUtils');
    ga.addParam('sysparm_name', 'getOpenCountForUser'); // method name
    ga.addParam('sysparm_user_id', newValue);           // custom parameter

    ga.getXMLAnswer(function(answer) {
        // answer is the string returned by the Script Include method
        var count = parseInt(answer);
        if (count > 10) {
            g_form.showFieldMsg('caller_id', 'This user has ' + count + ' open incidents', 'warning');
        }
    });
}

Key points about GlideAjax:

  • sysparm_name must match the method name in the Script Include exactly
  • All custom parameters must start with sysparm_
  • getXMLAnswer(callback) is asynchronous — the callback runs when the server responds
  • Never use getXML() or synchronous calls — they freeze the browser
  • The return value from the Script Include method becomes the answer argument in the callback

For a deeper walkthrough of GlideAjax with full examples, see the complete GlideAjax guide.

Returning complex data from a client-callable Script Include

When you need to return multiple values, JSON-encode the return value on the server and parse it on the client:

// Server side — Script Include method
getUserDetails: function() {
    var userId = this.getParameter('sysparm_user_id');
    var result = { found: false };

    var gr = new GlideRecord('sys_user');
    if (gr.get(userId)) {
        result.found = true;
        result.name = gr.getDisplayValue('name');
        result.email = gr.getValue('email');
        result.department = gr.getDisplayValue('department');
        result.manager = gr.getDisplayValue('manager');
    }

    return JSON.stringify(result);
},

// Client side — Client Script callback
ga.getXMLAnswer(function(answer) {
    var data = JSON.parse(answer);
    if (data.found) {
        g_form.setValue('department', data.department);
        g_form.setValue('manager', data.manager);
    }
});

Calling a Script Include from Flow Designer

Script Includes can be called from Flow Designer through the Run Script action or the Script step in Subflows. This is the recommended pattern for complex logic in flows that is reusable:

// In a Flow Designer Run Script action:
var utils = new IncidentUtils(fd_data.assignment_group, fd_data.priority);
var count = utils.getOpenCount();
fd_data.open_count = count;

Using a Script Include instead of writing logic directly in a Flow action makes the flow easier to read and makes the logic testable in Scripts - Background independently of the flow.

Calling a Script Include from a Scheduled Job

// In a Scheduled Script Execution:
var reportUtils = new WeeklyReportUtils();
reportUtils.generateAndSendReport();
gs.log('Weekly report generated at ' + new GlideDateTime().getValue());

Scheduled Jobs are a common use case for Script Includes — the job itself is minimal, and the logic lives in a well-tested Script Include. For more on Scheduled Jobs, see the guide to Scheduled Script Executions in ServiceNow.

Calling a Script Include from a REST API Scripted Processor

// In a Scripted REST API resource script:
(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {

    var body = request.body.data;
    var processor = new RequestProcessorUtils(body.requestType);
    var result = processor.processRequest(body);

    response.setContentType('application/json');
    response.setStatus(200);
    response.setBody(result);

})(request, response);

This pattern keeps Scripted REST API resource scripts clean and delegates all business logic to a testable Script Include. For building Scripted REST APIs, see the guide to building Scripted REST APIs.

Global vs scoped Script Includes

Script Includes can live in the global scope or in a scoped application. The access rules:

  • Global Script Includes are accessible from any script anywhere in the instance — Business Rules, Client Scripts, Scheduled Jobs, other Script Includes, everything
  • Scoped Script Includes are only accessible from scripts within the same application scope by default. Cross-scope access requires explicit configuration (the "Accessible from" field on the Script Include record)

Best practice for custom development: always work in a scoped application rather than global. This makes your customisations portable, upgradeable, and clearly separated from platform code. See the guide to scoped applications for the full architecture picture.

In a scoped Script Include, reference other tables and fields using the full scoped name where required:

// In a scoped app (x_mycompany_myapp scope):
var ScopedUtils = Class.create();
ScopedUtils.prototype = {
    initialize: function() {},

    getCustomTableCount: function() {
        // Custom table in the same scope — full scoped name
        var gr = new GlideRecord('x_mycompany_myapp_custom_table');
        gr.addEncodedQuery('active=true');
        gr.query();
        return gr.getRowCount();
    },

    type: 'ScopedUtils'
};

Testing Script Includes in Scripts - Background

Scripts - Background (System Definition > Scripts - Background) is the fastest way to test Script Include logic without triggering Business Rules or saving records. You can instantiate any Script Include and call its methods directly:

// Test your Script Include immediately in Scripts - Background
var utils = new IncidentUtils('GROUP_SYS_ID', '1');

gs.log('Open P1 count: ' + utils.getOpenCount());
gs.log('Oldest open: ' + utils.getOldestOpen());

// Test edge cases
var emptyUtils = new IncidentUtils('NONEXISTENT_GROUP', '1');
gs.log('Empty result: ' + emptyUtils.getOpenCount()); // Should return 0

Always test with the same parameters your production code will use. If a Script Include is used in a Business Rule that fires on P1 incident saves, test it with a real P1 incident sys_id.

For a complete debugging toolkit — including session debug, the Script Debugger, and try/catch patterns — see the guide to debugging ServiceNow scripts.

Error handling in Script Includes

Script Includes should handle errors gracefully and never let exceptions propagate silently. The recommended pattern:

var RobustUtils = Class.create();
RobustUtils.prototype = {
    initialize: function() {
        this.log = new GSLog('com.mycompany.robust_utils', 'RobustUtils');
    },

    processRecord: function(sysId) {
        try {
            if (!sysId) {
                gs.logWarning('RobustUtils.processRecord called with empty sysId', 'RobustUtils');
                return null;
            }

            var gr = new GlideRecord('incident');
            if (!gr.get(sysId)) {
                gs.logWarning('Incident not found: ' + sysId, 'RobustUtils');
                return null;
            }

            // main logic here
            return this._doWork(gr);

        } catch (e) {
            gs.logError('RobustUtils.processRecord failed for ' + sysId + ': ' + e.message, 'RobustUtils');
            return null;
        }
    },

    _doWork: function(gr) {
        // Private method convention — prefix with underscore
        return gr.getValue('number');
    },

    type: 'RobustUtils'
};

Key patterns in this example:

  • Validate inputs at the start of every method
  • Use try/catch for operations that could throw — GlideRecord gets, JSON parsing, external calls
  • Log errors with enough context to debug later: method name, the value that caused the failure
  • Return null or a safe default on failure — never let the caller crash because the Script Include threw
  • Prefix private methods with underscore (convention only — JavaScript does not enforce private methods)

Script Include naming conventions

Consistent naming makes Script Includes easier to find and understand:

  • PascalCaseIncidentUtils, ChangeRequestProcessor, UserManagementHelper
  • Suffix with Utils, Helper, Manager, or Processor based on the role: Utils for general utilities, Helper for supporting a specific feature, Manager for lifecycle operations, Processor for transformation logic
  • Company prefix in global scopeAcmeIncidentUtils avoids collisions with other customisations and upgrade conflicts
  • Ajax suffix for client-callableIncidentAjax, UserAjax — makes them easy to identify

Common mistakes

Mistake 1 — Forgetting the return statement

// Wrong — returns undefined
getCount: function() {
    var ga = new GlideAggregate('incident');
    ga.addAggregate('COUNT');
    ga.query();
    if (ga.next()) {
        var count = ga.getAggregate('COUNT'); // assigned but not returned
    }
},

// Right
getCount: function() {
    var ga = new GlideAggregate('incident');
    ga.addAggregate('COUNT');
    ga.query();
    if (ga.next()) {
        return parseInt(ga.getAggregate('COUNT'));
    }
    return 0; // always return a safe default
},

Mistake 2 — Type mismatch between record name and variable

// Record named "IncidentUtils" but script says:
var Incident_Utils = Class.create(); // Wrong — name mismatch
// type: 'Incident_Utils' // Also wrong

// Must match:
var IncidentUtils = Class.create();
// type: 'IncidentUtils'

Mistake 3 — Calling a non-client-callable Script Include from a Client Script

Only Script Includes with the Client callable checkbox checked and extending AbstractAjaxProcessor can be called from client-side scripts. Any other Script Include will fail silently or throw an error when called via GlideAjax.

Mistake 4 — Using synchronous GlideAjax

// Wrong — synchronous, deprecated, freezes the browser
var ga = new GlideAjax('IncidentAjaxUtils');
ga.addParam('sysparm_name', 'getCount');
ga.getXML(); // Do not use this

// Right — asynchronous with callback
ga.getXMLAnswer(function(answer) {
    // handle response here
});

Mistake 5 — Putting business logic in Business Rules instead of Script Includes

If a Business Rule contains more than 10–15 lines of logic, it should almost certainly be refactored into a Script Include. Business Rules that grow to 100+ lines become impossible to test, difficult to reuse, and create maintenance problems. The Business Rule should instantiate a Script Include and call a well-named method — that is its job.

When to use Script Includes vs inline scripts

Use a Script Include when:

  • The same logic is needed in more than one place
  • The logic is complex enough that it deserves its own unit tests
  • You need to call server-side code from a Client Script
  • The logic is likely to be reused in future development

Keep logic inline when:

  • It is genuinely one-off and will never be reused
  • It is extremely simple — one or two lines that would be more confusing extracted into a separate file

When in doubt, Script Include. The cost of extraction is low; the cost of maintaining duplicated inline logic is high.

Script Includes and the broader scripting ecosystem

Script Includes sit at the centre of ServiceNow server-side scripting. They are called by Business Rules, Scheduled Jobs, REST APIs, and Flow Designer scripts. They call GlideRecord, GlideAggregate, GlideSystem (gs), and other platform APIs. Understanding Script Includes deeply means understanding how all these pieces connect.

Related guides that complete the picture:

50 scripting patterns like this — in one guide

The NowSpectrum Pro Tips and Tricks guide covers 50 battle-tested developer patterns including Script Includes, GlideRecord, GlideAjax, Flow Designer, and debugging techniques.

Get the Pro Tips Guide →
← Back to all posts