AR
AgentRuss
ServiceNow Script Include architecture showing reusable server-side logic shared by Business Rules, UI Actions, APIs, scheduled jobs, and GlideAjax.
AgentRuss Guide

ServiceNow Script Includes Explained: Reusable Server-Side Logic

Written by
AgentRuss
Published

As ServiceNow applications grow, duplicated server-side logic becomes one of the easiest maintenance problems to create.

A Business Rule needs to calculate something.

A UI Action needs the same calculation.

Later, a Scheduled Script Execution needs it too.

Then a Scripted REST API needs almost the same logic.

The quickest solution is often to copy and paste the code.

That works initially, but now the application has several implementations of the same business rule.

When the requirement changes, every copy needs to be found and updated.

A Script Include gives that reusable logic one home.

Instead of duplicating the implementation, other server-side scripts call the Script Include.

In this guide, we'll look at:

  • what Script Includes are
  • when to use them
  • how to create reusable classes and methods
  • how they work with GlideRecord
  • how Business Rules should call them
  • client-callable Script Includes and GlideAjax
  • application scope and accessibility
  • common design mistakes
  • practical patterns for maintainable ServiceNow applications

What Is a Script Include?

A Script Include is reusable JavaScript stored on the ServiceNow server.

It can contain:

  • functions
  • classes
  • helper methods
  • reusable queries
  • business calculations
  • validation logic
  • data transformation
  • integration helpers

Other scripts can call that logic instead of implementing it again.

For example, imagine several parts of an application need to determine whether an Incident should be considered critical.

Instead of duplicating that rule, we can create one Script Include.

JavaScript
1var IncidentUtils =
2 Class.create()
3
4IncidentUtils.prototype = {
5 initialize:
6 function() {},
7
8 isCritical:
9 function(incidentGR) {
10 return (
11 incidentGR.getValue(
12 'priority',
13 ) === '1'
14 )
15 },
16
17 type:
18 'IncidentUtils',
19}

A server-side caller can then use:

JavaScript
1var utils =
2 new IncidentUtils()
3
4if (
5 utils.isCritical(
6 current,
7 )
8) {
9 gs.info(
10 'Critical incident'
11 )
12}

Now the definition of "critical" exists in one place.


Why Script Includes Matter

Without reusable server-side logic, applications tend to develop code like this:

Business Rule

JavaScript
1if (
2 current.priority == 1 &&
3 current.active == true
4) {
5 // logic
6}

UI Action

JavaScript
1if (
2 current.priority == 1 &&
3 current.active == true
4) {
5 // almost the same logic
6}

Scheduled Script

JavaScript
1if (
2 incidentGR.priority == 1 &&
3 incidentGR.active == true
4) {
5 // another copy
6}

Now imagine the business rule changes.

Critical incidents must also belong to a particular service.

Every implementation needs updating.

If one copy is missed, the application behaves inconsistently.

A Script Include lets every caller use the same implementation.


A Useful Mental Model

Think of the relationship like this:

Business Rule

Decides when reusable logic should run.

Script Include

Contains the reusable logic itself.

For example:

When an Incident changes to Critical, run our escalation calculation.

The Business Rule detects the record lifecycle event.

The Script Include performs the reusable calculation.

This keeps Business Rules smaller and easier to understand.


Script Includes Are Server-Side by Default

A normal Script Include runs on the ServiceNow server.

That means it can use server-side APIs such as:

JavaScript
1GlideRecord

and:

JavaScript
1gs

For example:

JavaScript
1var IncidentUtils =
2 Class.create()
3
4IncidentUtils.prototype = {
5 initialize:
6 function() {},
7
8 getActiveCriticalCount:
9 function() {
10 var incidentGA =
11 new GlideAggregate(
12 'incident',
13 )
14
15 incidentGA.addQuery(
16 'active',
17 true,
18 )
19
20 incidentGA.addQuery(
21 'priority',
22 '1',
23 )
24
25 incidentGA.addAggregate(
26 'COUNT',
27 )
28
29 incidentGA.query()
30
31 if (
32 incidentGA.next()
33 ) {
34 return parseInt(
35 incidentGA.getAggregate(
36 'COUNT',
37 ),
38 10,
39 )
40 }
41
42 return 0
43 },
44
45 type:
46 'IncidentUtils',
47}

The caller does not need to know how the count is calculated.

It simply asks:

JavaScript
1var utils =
2 new IncidentUtils()
3
4var count =
5 utils.getActiveCriticalCount()

A Basic Class-Based Script Include

A common Script Include structure looks like:

JavaScript
1var ExampleUtils =
2 Class.create()
3
4ExampleUtils.prototype = {
5 initialize:
6 function() {},
7
8 doSomething:
9 function() {
10 return 'result'
11 },
12
13 type:
14 'ExampleUtils',
15}

There are three important pieces.

Class.create()

Creates the class structure.

prototype

Defines the methods available on instances.

type

Identifies the class.

For a normal class-style Script Include, keep the Script Include name and class name consistent.


Creating an Instance

Once the Script Include exists, another server script can create it using:

JavaScript
1var utils =
2 new ExampleUtils()

Then call:

JavaScript
1var result =
2 utils.doSomething()

This works from server-side contexts that can access the Script Include.


Pass Data Into Methods Explicitly

Reusable methods are easier to understand when their inputs are clear.

For example:

JavaScript
1calculateScore:
2 function(
3 impact,
4 urgency,
5 ) {
6 return (
7 parseInt(
8 impact,
9 10,
10 ) *
11 parseInt(
12 urgency,
13 10,
14 )
15 )
16 }

Then:

JavaScript
1var utils =
2 new IncidentUtils()
3
4var score =
5 utils.calculateScore(
6 current.getValue(
7 'impact',
8 ),
9 current.getValue(
10 'urgency',
11 ),
12 )

Anyone reading the call can see what the method requires.


Avoid Hidden Dependence on current

Inside a Business Rule, current is available automatically.

That can tempt developers to write reusable logic that assumes current exists everywhere.

For example, avoid designing a Script Include method around an invisible dependency such as:

JavaScript
1doSomething:
2 function() {
3 return current.priority
4 }

Now the method only works in contexts where current happens to exist.

A more reusable method accepts what it needs:

JavaScript
1isCritical:
2 function(recordGR) {
3 return (
4 recordGR.getValue(
5 'priority',
6 ) === '1'
7 )
8 }

Then the caller decides which record to provide.


Business Rules Should Stay Small

Consider a Business Rule containing:

  • several GlideRecord queries
  • complex calculations
  • formatting logic
  • validation
  • related-record processing
  • integration logic

The Business Rule may technically work, but its responsibility becomes difficult to understand.

A cleaner Business Rule might look like:

JavaScript
1(function executeRule(
2 current,
3 previous,
4) {
5 var service =
6 new IncidentService()
7
8 service.handleEscalation(
9 current,
10 )
11})(current, previous)

Now the Business Rule communicates:

When this record condition occurs, handle escalation.

The reusable logic lives elsewhere.


Don't Move Everything Out of Business Rules

The goal is not to make every Business Rule contain exactly one line.

Simple logic can remain simple.

For example:

JavaScript
1current.u_last_processed =
2 gs.nowDateTime()

does not necessarily need a separate Script Include.

Create reusable abstractions when they provide real value.

Typical reasons include:

  • logic is used in several places
  • the implementation is complex
  • it deserves independent testing
  • the Business Rule is becoming difficult to read
  • the logic represents a reusable application service

Script Includes Can Contain GlideRecord Queries

Suppose several scripts need to retrieve active critical Incidents.

Instead of repeating:

JavaScript
1var incidentGR =
2 new GlideRecord('incident')
3
4incidentGR.addQuery(
5 'active',
6 true,
7)
8
9incidentGR.addQuery(
10 'priority',
11 '1',
12)
13
14incidentGR.query()

we can create a reusable method.

JavaScript
1var IncidentUtils =
2 Class.create()
3
4IncidentUtils.prototype = {
5 initialize:
6 function() {},
7
8 getActiveCritical:
9 function() {
10 var results = []
11
12 var incidentGR =
13 new GlideRecord(
14 'incident',
15 )
16
17 incidentGR.addQuery(
18 'active',
19 true,
20 )
21
22 incidentGR.addQuery(
23 'priority',
24 '1',
25 )
26
27 incidentGR.query()
28
29 while (
30 incidentGR.next()
31 ) {
32 results.push(
33 incidentGR
34 .getUniqueValue()
35 )
36 }
37
38 return results
39 },
40
41 type:
42 'IncidentUtils',
43}

The callers no longer need to know the query implementation.


Return Useful Data

Think carefully about what your Script Include should return.

Possible return types include:

  • boolean
  • string
  • number
  • array
  • object
  • sys_id
  • GlideRecord

For example:

JavaScript
1isCritical:
2 function(incidentGR) {
3 return (
4 incidentGR.getValue(
5 'priority',
6 ) === '1'
7 )
8 }

returns a boolean.

That makes the caller simple:

JavaScript
1if (
2 utils.isCritical(
3 current,
4 )
5) {
6 // ...
7}

Prefer Predictable Return Types

Avoid a method that sometimes returns:

  • a GlideRecord
  • false
  • an empty string
  • an object

depending on what happened.

That forces every caller to understand several unrelated cases.

Prefer predictable contracts.

For example:

JavaScript
1getUserEmail:
2 function(userSysId) {
3 if (!userSysId) {
4 return ''
5 }
6
7 var userGR =
8 new GlideRecord(
9 'sys_user',
10 )
11
12 if (
13 !userGR.get(
14 userSysId,
15 )
16 ) {
17 return ''
18 }
19
20 return (
21 userGR.getValue(
22 'email',
23 ) || ''
24 )
25 }

The caller knows the method always returns a string.


Validate Method Inputs

Reusable methods may eventually be called from places you did not originally expect.

Validate important inputs.

For example:

JavaScript
1getUserEmail:
2 function(userSysId) {
3 if (!userSysId) {
4 return ''
5 }
6
7 // continue
8 }

For more complex operations, validate:

  • required IDs
  • expected values
  • record existence
  • permissions where relevant
  • input format

A reusable method should not assume every caller is perfect.


Keep Methods Focused

A method called:

JavaScript
1getUserEmail()

should probably retrieve an email address.

It should not also:

  • update the user
  • create an Incident
  • send an event
  • close another task

Method names should accurately describe their responsibility.

This makes Script Includes easier to trust and reuse.


Avoid the Giant Utils Class

One of the easiest Script Include problems to create is:

JavaScript
1GlobalUtils

with 80 unrelated methods.

It might eventually contain:

  • Incident calculations
  • user queries
  • date formatting
  • group membership
  • asset logic
  • approval logic
  • integrations

Technically, the code is reusable.

Architecturally, it has become a dumping ground.

Prefer focused Script Includes.

For example:

  • IncidentUtils
  • UserUtils
  • AssignmentService
  • ApprovalService
  • AssetUtils

The right names depend on the application.


Utility vs Service Style

Not every Script Include has to be called Utils.

A useful distinction is:

Utility

Small reusable helpers.

For example:

JavaScript
1DateUtils
2StringUtils
3IncidentUtils

Service

Represents a larger application responsibility.

For example:

JavaScript
1IncidentEscalationService
2AssignmentService
3ApprovalService

A service may coordinate several queries and business rules around one feature.

Naming the Script Include after its responsibility makes the architecture clearer.


Example: Assignment Service

Imagine assignment logic is used from:

  • a Business Rule
  • a UI Action
  • a Scripted REST API

Instead of duplicating the logic, create:

JavaScript
1var AssignmentService =
2 Class.create()
3
4AssignmentService.prototype = {
5 initialize:
6 function() {},
7
8 assignIncident:
9 function(
10 incidentGR,
11 groupSysId,
12 ) {
13 if (
14 !incidentGR ||
15 !incidentGR.isValidRecord()
16 ) {
17 return false
18 }
19
20 if (!groupSysId) {
21 return false
22 }
23
24 incidentGR.setValue(
25 'assignment_group',
26 groupSysId,
27 )
28
29 incidentGR.update()
30
31 return true
32 },
33
34 type:
35 'AssignmentService',
36}

Now several server-side entry points can use the same assignment implementation.


Be Intentional About Who Performs update()

Reusable logic should have a clear contract around database persistence.

Compare these two method designs.

Method changes the record only

JavaScript
1applyDefaults:
2 function(recordGR) {
3 recordGR.setValue(
4 'priority',
5 '3',
6 )
7 }

The caller remains responsible for saving it.

Method performs the operation

JavaScript
1assignIncident:
2 function(
3 recordGR,
4 groupSysId,
5 ) {
6 recordGR.setValue(
7 'assignment_group',
8 groupSysId,
9 )
10
11 return recordGR.update()
12 }

Both designs can be valid.

The important thing is that callers understand whether the method:

  • only modifies an object
  • or performs a database operation

Hidden database writes can make reusable methods dangerous.


Name Methods to Reveal Side Effects

A method called:

JavaScript
1getAssignmentGroup()

sounds read-only.

It should not silently update records.

A method called:

JavaScript
1assignToGroup()

clearly suggests a state change.

Good naming makes side effects easier to understand before reading the implementation.


Script Includes Can Reduce Repeated GlideRecord Code

Suppose five different scripts need a user record.

A reusable method might be:

JavaScript
1getUser:
2 function(userSysId) {
3 if (!userSysId) {
4 return null
5 }
6
7 var userGR =
8 new GlideRecord(
9 'sys_user',
10 )
11
12 if (
13 !userGR.get(
14 userSysId,
15 )
16 ) {
17 return null
18 }
19
20 return userGR
21 }

This can reduce repeated retrieval logic.

But do not create a wrapper around every single GlideRecord operation just because you can.

A reusable method should represent something meaningful to the application.


Reuse Business Meaning, Not Just Syntax

This method:

JavaScript
1queryTable:
2 function(
3 table,
4 field,
5 value,
6 ) {
7 // generic query
8 }

may not add much value beyond GlideRecord itself.

But:

JavaScript
1getActiveApprovers:
2 function(requestSysId) {
3 // application-specific logic
4 }

expresses meaningful business behavior.

The best reusable methods often hide business complexity, not merely JavaScript syntax.


Keep Database Filtering Inside the Query

The same GlideRecord best practices apply inside Script Includes.

Prefer:

JavaScript
1incidentGR.addQuery(
2 'active',
3 true,
4)
5
6incidentGR.addQuery(
7 'priority',
8 '1',
9)

rather than retrieving every Incident and filtering in JavaScript.

A Script Include does not make an inefficient query efficient.


Avoid Queries Inside Large Loops

Script Includes can make expensive logic look harmless.

For example:

JavaScript
1getManager:
2 function(userSysId) {
3 var userGR =
4 new GlideRecord(
5 'sys_user',
6 )
7
8 if (
9 userGR.get(
10 userSysId,
11 )
12 ) {
13 return userGR.getValue(
14 'manager',
15 )
16 }
17
18 return ''
19 }

This may be fine when called once.

If another script calls it 5,000 times inside a loop, it could generate thousands of queries.

Reusable methods still need performance awareness.


Think About the Caller

When creating reusable code, ask:

How often will this method be called?

A function used once per form submission is different from a function called for every record in a 100,000-record batch.

Consider:

  • query count
  • result size
  • caching opportunities
  • repeated reference lookups
  • transaction timing

Abstraction does not remove performance costs.


Classless or On-Demand Script Includes

Not every Script Include needs to define a class.

ServiceNow also supports simple reusable functions.

Conceptually, an on-demand Script Include might expose one function whose name matches the Script Include.

For example:

JavaScript
1function calculateSomething(
2 value,
3) {
4 return (
5 parseInt(
6 value,
7 10,
8 ) * 2
9 )
10}

This can be useful for a small reusable function.

For larger groups of related methods, a class-based Script Include is usually easier to organize.


Imagine an Incident utility needs:

  • isCritical()
  • getOpenChildCount()
  • getAssignmentGroup()

Keeping related methods together can make sense.

For example:

JavaScript
1var IncidentUtils =
2 Class.create()
3
4IncidentUtils.prototype = {
5 initialize:
6 function() {},
7
8 isCritical:
9 function(incidentGR) {
10 return (
11 incidentGR.getValue(
12 'priority',
13 ) === '1'
14 )
15 },
16
17 getOpenChildCount:
18 function(
19 incidentSysId,
20 ) {
21 // query here
22 },
23
24 type:
25 'IncidentUtils',
26}

The class provides a clear namespace for related functionality.


Script Includes and Application Scope

ServiceNow applications can run in different scopes.

A Script Include created inside a scoped application belongs to that application scope.

When deciding whether other scopes should be able to use it, consider:

  • who actually needs the API
  • whether cross-scope use is intended
  • whether the methods expose sensitive operations
  • whether the implementation should remain internal

Do not expose reusable logic more broadly than necessary.


Treat Script Includes Like Application APIs

Once several parts of your application depend on a Script Include, its methods effectively become an internal API.

For example:

JavaScript
1service.getAssignment(
2 recordSysId,
3)

Other code now depends on:

  • the method name
  • parameters
  • return type
  • behavior

Changing that contract can break multiple callers.

That means Script Includes deserve thoughtful method design.


Keep Public Methods Stable

Suppose several callers use:

JavaScript
1getCriticalIncidents()

Changing it suddenly to require several new parameters may break those callers.

As Script Includes become widely used, treat changes carefully.

For larger applications, think about:

  • backward compatibility
  • method contracts
  • input validation
  • predictable returns

This is ordinary API design applied inside ServiceNow.


Private Helper Methods

A Script Include may contain helper methods that exist only to support its public methods.

For example:

JavaScript
1var IncidentService =
2 Class.create()
3
4IncidentService.prototype = {
5 initialize:
6 function() {},
7
8 calculatePriority:
9 function(
10 impact,
11 urgency,
12 ) {
13 var score =
14 this._calculateScore(
15 impact,
16 urgency,
17 )
18
19 return this
20 ._priorityFromScore(
21 score,
22 )
23 },
24
25 _calculateScore:
26 function(
27 impact,
28 urgency,
29 ) {
30 return (
31 parseInt(
32 impact,
33 10,
34 ) *
35 parseInt(
36 urgency,
37 10,
38 )
39 )
40 },
41
42 _priorityFromScore:
43 function(score) {
44 if (score <= 1) {
45 return '1'
46 }
47
48 return '3'
49 },
50
51 type:
52 'IncidentService',
53}

A naming convention such as a leading underscore can communicate that a method is intended as an internal helper.

It is still JavaScript rather than true language-level privacy, but the intention is clearer.


Script Includes Are Easier to Test Than Duplicated Logic

Suppose a priority calculation lives directly inside several Business Rules.

Testing it requires exercising each Business Rule context.

If the calculation lives in:

JavaScript
1IncidentService.calculatePriority()

you can test that method independently with known inputs.

For example:

JavaScript
1var service =
2 new IncidentService()
3
4gs.info(
5 service.calculatePriority(
6 '1',
7 '1',
8 )
9)

This makes development and troubleshooting easier.


Background Scripts Are Useful for Testing

In a development instance, Background Scripts can be useful for quickly exercising server-side Script Includes.

For example:

JavaScript
1var utils =
2 new IncidentUtils()
3
4var result =
5 utils.getActiveCriticalCount()
6
7gs.info(
8 'Result: ' +
9 result
10)

This lets you verify the reusable logic separately from its Business Rule or other caller.

Use appropriate care when testing code that performs updates or deletes.


Log Meaningful Information During Development

When troubleshooting a reusable method, logs can help reveal:

  • which inputs were provided
  • which path was taken
  • how many records matched
  • what result was returned

For example:

JavaScript
1gs.debug(
2 'IncidentService: processing ' +
3 incidentSysId
4)

Avoid leaving excessive noisy logging in heavily executed production paths.


Throwing Errors vs Returning Failure

Reusable methods need a consistent error strategy.

A validation-style method may simply return:

JavaScript
1false

A lookup may return:

JavaScript
1null

or:

JavaScript
1''

A more serious unexpected failure may deserve explicit error handling.

The right approach depends on the method.

What matters is consistency.

Callers should know what failure looks like.


Don't Swallow Every Error

Avoid patterns that silently hide unexpected failures.

For example:

JavaScript
1try {
2 // everything
3} catch (e) {
4 return false
5}

Now a programming error and a normal validation failure look identical.

When errors matter, log or handle enough context to make them diagnosable.


Client-Callable Script Includes

So far, we've discussed server-to-server reuse.

But sometimes a Client Script needs information that must be calculated on the server.

That is where a client-callable Script Include can be used with GlideAjax.

The flow is:

Client Script

GlideAjax

Client-callable Script Include

→ server-side processing

→ result returned to the browser

This gives the browser access to a controlled server-side operation without moving server logic into the Client Script.


Why Use GlideAjax?

Imagine a Client Script needs to know a user's manager.

The browser does not need an entire user record.

It needs one answer.

Instead of pulling unnecessary data to the client, a GlideAjax call can ask a Script Include:

What is this user's manager?

The Script Include performs the server-side work and returns the result.


Client-Callable Script Includes Need Explicit Configuration

Normal Script Includes are not automatically intended for direct client calls.

For GlideAjax use, configure the Script Include appropriately as client callable.

This makes the server-side endpoint available to client-side callers according to the platform's access rules.

Do not make every Script Include client callable.

Expose only what actually needs to be reached from the client.


A GlideAjax Script Include

A common client-callable structure extends AbstractAjaxProcessor.

For example:

JavaScript
1var UserAjax =
2 Class.create()
3
4UserAjax.prototype =
5 Object.extendsObject(
6 global.AbstractAjaxProcessor,
7 {
8 getManagerName:
9 function() {
10 var userSysId =
11 this.getParameter(
12 'sysparm_user_id',
13 )
14
15 if (!userSysId) {
16 return ''
17 }
18
19 var userGR =
20 new GlideRecord(
21 'sys_user',
22 )
23
24 if (
25 !userGR.get(
26 userSysId,
27 )
28 ) {
29 return ''
30 }
31
32 return userGR
33 .manager
34 .getDisplayValue()
35 },
36
37 type:
38 'UserAjax',
39 },
40 )

The method reads parameters sent by the client and returns the requested result.


Calling It From a Client Script

A Client Script could call that Script Include with:

JavaScript
1var ga =
2 new GlideAjax(
3 'UserAjax',
4 )
5
6ga.addParam(
7 'sysparm_name',
8 'getManagerName',
9)
10
11ga.addParam(
12 'sysparm_user_id',
13 g_form.getValue(
14 'caller_id',
15 ),
16)
17
18ga.getXMLAnswer(
19 function(answer) {
20 if (answer) {
21 g_form.setValue(
22 'u_manager_name',
23 answer,
24 )
25 }
26 },
27)

The important architectural split is:

Client Script

Handles the form.

Script Include

Handles the server-side data retrieval.


Keep GlideAjax Asynchronous

Client-server communication introduces latency.

Use asynchronous patterns so the browser is not unnecessarily blocked while waiting for the server.

The client sends the request.

Other browser activity can continue.

When the response arrives, the callback processes it.

This provides a much better user experience than blocking client execution while waiting for the server.


Return Only What the Client Needs

Suppose the browser needs:

  • manager name

Do not return an entire large record structure.

Return the smallest useful result.

For example:

JavaScript
1return userGR
2 .manager
3 .getDisplayValue()

This reduces:

  • network payload
  • parsing
  • unnecessary data exposure
  • client-side complexity

Returning Multiple Values

Sometimes the client genuinely needs several related values.

One option is to return JSON.

For example:

JavaScript
1var result = {
2 name:
3 userGR.getDisplayValue(),
4
5 email:
6 userGR.getValue(
7 'email',
8 ),
9
10 manager:
11 userGR
12 .manager
13 .getDisplayValue(),
14}
15
16return JSON.stringify(
17 result,
18)

Then the client can parse the response.

Only include fields the browser actually needs.


Validate Client-Supplied Parameters

A GlideAjax method receives values from the client.

Do not automatically trust those inputs just because the request came from your own Client Script.

Validate:

  • required parameters
  • referenced records
  • allowed values
  • permissions where appropriate

The browser is not an authoritative security boundary.


Client Callable Does Not Mean Public Business Authority

A client-callable Script Include should not blindly perform privileged operations simply because the client requested them.

For example:

Delete any Incident whose sys_id I send.

would be a dangerous server API without proper authorization and validation.

Client-callable methods should be designed with the same care as other server endpoints.


Don't Make One Ajax Script Include Do Everything

Another common mistake is creating:

JavaScript
1AjaxUtils

with dozens of unrelated client-callable methods.

As the application grows, it becomes difficult to:

  • understand
  • secure
  • test
  • maintain

Prefer endpoints grouped around meaningful responsibilities.

For example:

  • UserAjax
  • IncidentAjax
  • CatalogAjax

depending on the application's needs.


Separate Server-Only Logic From Client Exposure

Suppose you have a large server-side service:

JavaScript
1AssignmentService

The browser only needs one small piece of information from it.

You do not necessarily need to make the entire service client callable.

Instead, a small client-callable Script Include can call the server-side service internally.

That creates a clean boundary.

The architecture becomes:

Client Script

Ajax Script Include

Server Service Script Include

Database / business logic

This can keep client exposure much narrower.


Example: Reusing a Server Service From GlideAjax

A client-callable Script Include might contain:

JavaScript
1getSuggestedGroup:
2 function() {
3 var category =
4 this.getParameter(
5 'sysparm_category',
6 )
7
8 var service =
9 new AssignmentService()
10
11 return service
12 .getSuggestedGroup(
13 category,
14 )
15 }

Now the actual assignment algorithm remains in a reusable server-side service.

GlideAjax merely exposes the specific operation the browser needs.


Script Includes and Scripted REST APIs

A Scripted REST API may also need application logic.

Avoid implementing an important rule only inside the REST resource script.

Instead, the REST resource can:

  1. validate the request
  2. call a Script Include
  3. format the response

For example:

JavaScript
1var service =
2 new AssignmentService()
3
4var result =
5 service.assignIncident(
6 incidentGR,
7 groupSysId,
8 )

The same service can potentially be reused by other server-side entry points.


Script Includes and Scheduled Jobs

Scheduled scripts often need logic that is also used elsewhere.

For example:

Find stale Incidents and run escalation processing.

The Scheduled Script Execution can identify the records and call:

JavaScript
1var service =
2 new IncidentEscalationService()
3
4service.process(
5 incidentGR,
6)

The schedule decides when processing happens.

The service decides how escalation works.


Script Includes and UI Actions

A UI Action may initiate a business operation.

Instead of embedding a large amount of server-side logic directly in the UI Action, it can call a Script Include.

For example:

JavaScript
1var service =
2 new ApprovalService()
3
4service.requestApproval(
5 current,
6)

The UI Action remains an entry point.

The reusable service owns the process.


Script Includes and Business Rules

This is one of the most common combinations.

A Business Rule answers:

When should something run?

A Script Include answers:

How does the reusable business logic work?

For example:

JavaScript
1(function executeRule(
2 current,
3 previous,
4) {
5 var service =
6 new IncidentEscalationService()
7
8 service.process(
9 current,
10 )
11})(current, previous)

This makes the record-triggering behavior easy to understand.


Don't Call a Business Rule From Other Scripts

A Business Rule is designed around record lifecycle execution.

If a UI Action and Business Rule need the same business logic, do not attempt to make one entry point call the other.

Move the shared logic into a Script Include.

Then both can call it.

That creates a cleaner reusable boundary.


Keep Security Separate From Code Reuse

A Script Include helps centralize logic.

It does not automatically make the operation secure.

You still need to consider:

  • ACLs
  • roles
  • caller access
  • application scope
  • client-callable access
  • validation

Security requirements should be designed explicitly.


Avoid Hard-Coded sys_id Values

Reusable Script Includes should be especially careful about hard-coded identifiers.

For example:

JavaScript
1if (
2 groupSysId ===
3 '46f3...'
4) {
5 // ...
6}

may create problems when moving between:

  • development
  • test
  • production

where referenced records may differ.

When possible, use:

  • system properties
  • configuration tables
  • data-driven references
  • application configuration

Reusable code becomes more portable when environment-specific data stays outside it.


Avoid Hard-Coded Business Rules Too

A reusable method should not require code changes every time business configuration changes.

For example, instead of:

JavaScript
1if (
2 category === 'hardware'
3) {
4 group = '...'
5}

consider whether category-to-group mapping belongs in:

  • a configuration table
  • decision data
  • system properties
  • another appropriate platform configuration mechanism

Code should represent logic.

Configuration should represent values that administrators are expected to change.


Use Script Includes to Create Clear Boundaries

Good Script Includes often represent architectural boundaries.

For example:

IncidentEscalationService

Owns Incident escalation rules.

AssignmentService

Owns assignment decisions.

ApprovalService

Owns approval operations.

These are easier to reason about than a collection of unrelated scripts that each contain fragments of the same feature.


A Practical Script Include Checklist

Before creating a Script Include, ask:

Will this logic be reused?

A Script Include is a strong candidate.

Is the current script becoming difficult to read?

Extracting a meaningful responsibility may help.

Does the logic represent a reusable application operation?

Consider a focused service-style Script Include.

Is this merely three simple lines used once?

A new abstraction may not be necessary.

Does the method have clear inputs?

Pass them explicitly.

Does it have a predictable return type?

Make the method contract easy to understand.

Does it write to the database?

Make that behavior obvious.

Does the browser need to call it?

Only then consider client-callable configuration.

Is it exposed to the client?

Validate inputs and access carefully.

Does it query the database heavily?

Consider performance and query frequency.


Common Script Include Mistakes

Creating one massive utility class

Keep responsibilities focused.

Moving code without improving the architecture

A large Business Rule inside a large Script Include is still difficult code.

Depending implicitly on current

Pass the required record or values explicitly.

Returning unpredictable data types

Create clear method contracts.

Hiding database writes inside read-sounding methods

Make side effects obvious.

Copying the same GlideRecord query into many Script Includes

Centralize meaningful shared logic.

Making everything client callable

Expose only what the browser actually needs.

Trusting GlideAjax parameters

Validate client input.

Running database queries inside frequently called loops

Reusable does not mean free.

Hard-coding environment-specific identifiers

Keep configuration outside reusable code when possible.


A Better Layered Architecture

Imagine an Incident application with assignment logic.

A clean architecture could be:

Client Script

Detects a relevant form change.

GlideAjax

Requests a suggested assignment.

IncidentAjax

Provides the controlled client-callable endpoint.

AssignmentService

Contains the reusable server-side assignment logic.

GlideRecord

Retrieves required records.

Business Rule

Enforces the authoritative assignment rule when the record is saved.

Now each layer has a clear responsibility.


Entry Points vs Reusable Logic

One useful architectural distinction is between entry points and services.

Entry points include:

  • Business Rules
  • UI Actions
  • Scheduled Jobs
  • Scripted REST APIs
  • Client Scripts through GlideAjax

They answer:

What caused this operation to begin?

A Script Include can contain the reusable service that answers:

How should the operation work?

This separation makes ServiceNow applications much easier to evolve.


A Practical Example

Suppose the organization has one rule:

Determine the support group for an Incident based on category.

This rule is needed by:

  • a Business Rule during save
  • a Client Script for a suggested value
  • an integration
  • a Scheduled Script correcting legacy records

Instead of four implementations, create:

JavaScript
1var AssignmentService =
2 Class.create()
3
4AssignmentService.prototype = {
5 initialize:
6 function() {},
7
8 getGroupForCategory:
9 function(category) {
10 if (!category) {
11 return ''
12 }
13
14 var mappingGR =
15 new GlideRecord(
16 'u_assignment_mapping',
17 )
18
19 mappingGR.addQuery(
20 'u_category',
21 category,
22 )
23
24 mappingGR.addQuery(
25 'u_active',
26 true,
27 )
28
29 mappingGR.setLimit(1)
30
31 mappingGR.query()
32
33 if (
34 mappingGR.next()
35 ) {
36 return mappingGR.getValue(
37 'u_assignment_group',
38 )
39 }
40
41 return ''
42 },
43
44 type:
45 'AssignmentService',
46}

Now every caller shares one mapping implementation.


Business Rule Caller

The Business Rule might use:

JavaScript
1(function executeRule(
2 current,
3 previous,
4) {
5 var service =
6 new AssignmentService()
7
8 var groupSysId =
9 service
10 .getGroupForCategory(
11 current.getValue(
12 'category',
13 ),
14 )
15
16 if (groupSysId) {
17 current.setValue(
18 'assignment_group',
19 groupSysId,
20 )
21 }
22})(current, previous)

Because this could be a Before Business Rule, it can modify current without needing an additional current.update().


GlideAjax Caller

A client-callable wrapper could ask the same service:

JavaScript
1getSuggestedGroup:
2 function() {
3 var category =
4 this.getParameter(
5 'sysparm_category',
6 )
7
8 var service =
9 new AssignmentService()
10
11 return service
12 .getGroupForCategory(
13 category,
14 )
15 }

Now the browser and server-side enforcement use the same assignment rule.

That greatly reduces the risk of inconsistent behavior.


The Client Is Still Not the Authority

Even though the Client Script can request the suggested group, the server-side rule remains authoritative.

The browser may display:

Suggested Assignment Group: Service Desk

But when the record is saved, the Business Rule can still apply the real rule.

This follows the same principle from our Client Scripts vs Business Rules guide:

Client logic improves the interaction.
Server logic protects the business rule.

When Not to Use a Script Include

A Script Include is not necessary for every piece of server-side JavaScript.

You may not need one when:

  • the logic is extremely small
  • it is used only once
  • the behavior is tightly coupled to one specific record lifecycle event
  • extracting it would make the code harder to follow
  • a declarative platform feature already solves the requirement

Abstraction should reduce complexity.

It should not exist merely because reusable code is considered fashionable.


Prefer Platform Features When Appropriate

Script Includes are powerful, but they are still custom code.

Before writing one, consider whether the requirement is better handled through:

  • Flow Designer
  • Data Policies
  • UI Policies
  • ACLs
  • Decision Tables
  • platform configuration
  • another purpose-built ServiceNow capability

Good ServiceNow architecture uses scripting where scripting adds value.


A Useful Mental Model

Think of a ServiceNow application like this:

Entry Point

Business Rule
UI Action
Scheduled Job
REST API
GlideAjax

Script Include

Reusable application logic

Platform APIs

GlideRecord
GlideAggregate
GlideSystem
other APIs

Data / Platform

ServiceNow tables and services

The entry point can change without forcing the core business logic to be rewritten.

That is the main architectural value of Script Includes.


Why Script Includes Scale Well

As an application grows, the same business rules are often needed from more places.

Today:

  • one Business Rule

Tomorrow:

  • UI Action
  • Scheduled Job
  • Integration
  • GlideAjax call
  • Scripted REST API

If the real business logic already lives in a focused Script Include, adding another caller becomes much easier.

Instead of copying the implementation, the new entry point reuses the existing service.


Conclusion

Script Includes are one of the most important tools for building maintainable server-side ServiceNow applications.

They give reusable JavaScript logic a clear home.

Instead of duplicating the same implementation across:

  • Business Rules
  • UI Actions
  • Scheduled Jobs
  • REST APIs
  • other server scripts

those entry points can call one maintained implementation.

A strong Script Include should have:

  • a clear responsibility
  • meaningful method names
  • explicit inputs
  • predictable outputs
  • intentional database side effects
  • efficient queries
  • appropriate application access
  • careful client exposure

Client-callable Script Includes extend the same architecture to the browser through GlideAjax.

But client exposure should be deliberate.

Validate inputs, return only the required information, and keep authoritative business rules on the server.

A useful rule of thumb is:

Entry points decide when something happens.
Script Includes define reusable ways of doing it.

Once you begin thinking of Script Includes as internal application APIs rather than just miscellaneous helper files, ServiceNow server-side architecture becomes much easier to organize, reuse, test, and maintain.