Script Includes
Guide for creating ServiceNow Script Includes using the Fluent API. Script includes bundle reusable server-side logic into classes or utilities that can be called from business rules, scheduled scripts, other script includes, and client-side code via GlideAjax.
When to Use
- Creating reusable server-side logic (utility classes, helper functions)
- Building server-side APIs callable from client code via GlideAjax
- Sharing business logic across multiple business rules, scheduled scripts, or other server scripts
- Creating AJAX processors for UI Pages or client scripts
Instructions
- Use
Now.includefor scripts: The ScriptInclude API only accepts strings, so keep JavaScript in a standalone file and reference it withNow.include('../../server/script-includes/file.js'). This enables syntax highlighting and two-way sync. Note: Script Include class files (Class.create()) should NOT import Glide APIs — they are auto-available in the Script Include execution context. - Use ES5 syntax: Use the classic
Class.create()pattern or plain functions. ES6classsyntax is not supported on the ServiceNow platform for script includes. - Match
typeto class name: In theClass.create()prototype, thetypeproperty must exactly match the class name and thenameproperty in the Fluent definition. Mismatches cause runtime failures. - Set
clientCallablefor GlideAjax: If client-side code needs to call the script include, setclientCallable: true. Without this, GlideAjax calls will be rejected. - Extend
AbstractAjaxProcessorfor AJAX: When the script include is called via GlideAjax, extendglobal.AbstractAjaxProcessorand usethis.getParameter()to read client parameters. - Scope accessibility: Set
accessibleFrom: 'public'only when other scoped apps need access. Default to'package_private'for internal use.
Key Concepts
Class-Based vs Classless
- Class-based (most common): Use
Class.create()with a prototype. Best for grouping related methods. Thetypeproperty in the prototype must match the class name. - Classless (on-demand): A single function that runs when called by name. Best for one-off utility functions. The function name must match the
nameproperty.
GlideAjax Pattern
To make a script include callable from client-side code:
- Set
clientCallable: truein the Fluent definition - Extend
global.AbstractAjaxProcessorin the JavaScript - Use
this.getParameter('sysparm_name')to read parameters passed from the client - Return data as JSON strings for structured responses
Project Structure
src/
server/
script-includes/
my-utils.js <-- JavaScript business logic
fluent/
script-includes/
my-utils.now.ts <-- Fluent record definition
Bridging Modules Through Script Includes
Script includes have been the standard way to write reusable server-side code in ServiceNow for years. JavaScript modules are the modern replacement, but many platform features still rely on script includes — GlideAjax, cross-scope APIs, script include-based extension points, and integrations that call script includes by name. When your logic lives in a module but needs to be accessible through one of these legacy mechanisms, create a script include that acts as a thin bridge.
When to use this pattern:
- You have module code that needs to be callable via GlideAjax from client scripts
- Another scoped app needs to call your logic by script include name
- A platform feature (e.g., dynamic reference qualifier, condition script) expects a script include
- You want to keep your business logic in a module (with typed imports, testability, and code reuse) while still being accessible to legacy callers
How it works:
- Write your business logic in a module file with ES module syntax (
import/export) - Create a thin Script Include wrapper that uses
require()to load the module and delegates to it - Define the Fluent record pointing at the wrapper via
Now.include()
The wrapper .js file uses Class.create and calls require() to pull in the bundled module from ./dist/modules/. At build time, the SDK bundles your module into the dist/modules/ directory using repack. At runtime on the platform, require() resolves to that bundled output.
Example — exposing a module through a script include:
Module file (src/modules/server/string-utils.js):
export function capitalize(text) {
if (!text) return ''
return text.charAt(0).toUpperCase() + text.substring(1)
}
export function truncate(text, maxLength) {
if (!text || text.length <= maxLength) return text
return text.substring(0, maxLength) + '...'
}
Wrapper script (src/server/script-includes/string-utils.js):
var StringUtils = Class.create()
StringUtils.prototype = {
initialize: function () {
this._mod = require('./dist/modules/server/string-utils.js')
},
capitalize: function (text) {
return this._mod.capitalize(text)
},
truncate: function (text, maxLength) {
return this._mod.truncate(text, maxLength)
},
type: 'StringUtils',
}
Fluent definition (src/fluent/script-includes/string-utils.now.ts):
import '@servicenow/sdk/global'
import { ScriptInclude } from '@servicenow/sdk/core'
ScriptInclude({
$id: Now.ID['StringUtils'],
name: 'StringUtils',
script: Now.include('../../server/script-includes/string-utils.js'),
description: 'Bridge to string utility module',
})
Key rules for the wrapper:
- Keep the wrapper as thin as possible — it should only
require()the module and delegate. All business logic belongs in the module. - The wrapper uses
Class.createand must NOT import Glide APIs (they are auto-available in Script Include context). - The module file MUST import Glide APIs from
@servicenow/glide(they are NOT auto-available in module context). - The
require()path points to./dist/modules/...— this is the bundled output location. Match the path structure of your module source file undersrc/modules/. - The
typeproperty, class name, and Fluentnamemust all match exactly.
Avoidance
- Never use ES6
classsyntax -- the ServiceNow server runtime does not support it for script includes - Never mismatch
typeand class name -- thetypeproperty in the prototype, theClass.create()variable name, and thenamein the Fluent definition must all match exactly - Never forget
clientCallable: truefor GlideAjax -- client calls will silently fail without it - Avoid inline scripts for anything beyond a one-liner -- use
Now.include()for maintainability and syntax highlighting
API Reference
For the full property reference, see the scriptinclude-api topic.
Examples
Basic Script Include
Fluent definition (src/fluent/script-includes/math-utils.now.ts):
import '@servicenow/sdk/global'
import { ScriptInclude } from '@servicenow/sdk/core'
export const MathUtils = ScriptInclude({
$id: Now.ID['MathUtils'],
name: 'MathUtils',
script: Now.include('../../server/script-includes/math-utils.js'),
description: 'Basic math utility functions',
accessibleFrom: 'package_private',
})
JavaScript logic (src/server/script-includes/math-utils.js):
Important: Script Include class files (
Class.createpattern) must NOT import Glide APIs — they are auto-available in the Script Include execution context. See themodule-guidetopic.
var MathUtils = Class.create()
MathUtils.prototype = {
initialize: function () {},
multiply: function (a, b) {
return a * b
},
type: 'MathUtils', // IMPORTANT: must match the class name
}
GlideAjax Script Include (Client-Callable)
Fluent definition (src/fluent/script-includes/todo-ajax.now.ts):
import '@servicenow/sdk/global'
import { ScriptInclude } from '@servicenow/sdk/core'
export const TodoAjax = ScriptInclude({
$id: Now.ID['TodoAjax'],
name: 'TodoAjax',
script: Now.include('../../server/script-includes/todo-ajax.js'),
description: 'AJAX processor for to-do operations',
clientCallable: true,
accessibleFrom: 'public',
})
JavaScript logic (src/server/script-includes/todo-ajax.js):
var TodoAjax = Class.create()
TodoAjax.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {
getTasks: function () {
var tasks = []
var gr = new GlideRecord('x_snc_todo_item')
gr.query()
while (gr.next()) {
tasks.push({
sys_id: gr.getUniqueValue(),
task: gr.getValue('task'),
state: gr.getValue('state'),
})
}
return JSON.stringify(tasks)
},
addTask: function () {
var task = this.getParameter('sysparm_task')
var state = this.getParameter('sysparm_state') || 'ready'
var gr = new GlideRecord('x_snc_todo_item')
gr.initialize()
gr.setValue('task', task)
gr.setValue('state', state)
var sysId = gr.insert()
return JSON.stringify({
success: true,
task: { sys_id: sysId, task: task, state: state },
})
},
type: 'TodoAjax',
})