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
Class.create()for script includes: TheClass.create()pattern is the established ServiceNow convention for script includes and is the standard approach. ES6classsyntax works on the platform, butClass.create()is strongly preferred for consistency with the ServiceNow ecosystem and for compatibility withObject.extendsObjectandAbstractAjaxProcessor. - 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. - Set
sandboxCallableonly when needed: SetsandboxCallable: trueonly when the script include will be called from sandbox-evaluated expressions such asjavascript:query conditions, filter expressions, or column default values. Do not set it by default — it widens the security surface. - 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
Sandbox Callable Pattern
When a sandbox-evaluated expression (e.g., a scheduled script condition, javascript: filter, or column default value) needs complex logic that can't be written as a single expression, create a sandboxCallable script include to hold that logic. The sandbox expression then calls it as a single expression:
- Condition field:
new global.MyConditionChecker().shouldRun() - Filter query:
javascript:new global.MyFilterHelper().getFilterValue()
The script include body runs in the normal server-side context where var, if, loops, and GlideRecord are fully supported. Only the calling expression is restricted to a single expression.
Fluent definition (src/fluent/script-includes/sync-condition-checker.now.ts):
import '@servicenow/sdk/global'
import { ScriptInclude } from '@servicenow/sdk/core'
ScriptInclude({
$id: Now.ID['SyncConditionChecker'],
name: 'SyncConditionChecker',
script: Now.include('../../server/script-includes/sync-condition-checker.js'),
description: 'Checks whether daily sync should run',
sandboxCallable: true,
})
JavaScript logic (src/server/script-includes/sync-condition-checker.js):
var SyncConditionChecker = Class.create()
SyncConditionChecker.prototype = {
initialize: function () {},
shouldRun: function () {
if (gs.getProperty('x_myapp.sync.enabled', 'false') !== 'true') {
return false
}
var dayOfWeek = new GlideDateTime().getDayOfWeekLocalTime()
if (dayOfWeek < 2 || dayOfWeek > 6) {
return false
}
var gr = new GlideRecord('x_myapp_queue')
gr.addQuery('processed', false)
gr.setLimit(1)
gr.query()
return gr.hasNext()
},
type: 'SyncConditionChecker',
}
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
- Prefer
Class.create()over ES6class-- while ES6 classes work on the platform (note:asyncclass methods are disallowed; use async arrow functions instead),Class.create()is the ServiceNow convention and is required when extendingAbstractAjaxProcessorviaObject.extendsObject - 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',
})