The ScriptSync class provides bidirectional synchronization between local files and ServiceNow script records (Script Includes, Business Rules, Client Scripts, UI Scripts, and UI Actions).
The ScriptSync class enables you to:
- Pull script content from ServiceNow and write it to local files
- Push local file content to ServiceNow script records
- Sync all scripts in a directory based on a naming convention
- Parse and generate filenames in the
{name}.{type}.jsformat - Work with Script Includes, Business Rules, Client Scripts, UI Scripts, and UI Actions
| Key | Table | Label |
|---|---|---|
sys_script_include |
sys_script_include |
Script Include |
sys_script |
sys_script |
Business Rule |
sys_ui_script |
sys_ui_script |
UI Script |
sys_ui_action |
sys_ui_action |
UI Action |
sys_script_client |
sys_script_client |
Client Script |
constructor(instance: ServiceNowInstance)import { ServiceNowInstance, ScriptSync } from '@sonisoft/now-sdk-ext-core';
const scriptSync = new ScriptSync(instance);Pull a script from ServiceNow and write it to a local file. Queries the appropriate table by name and writes the script field content to the specified file path.
async pullScript(options: SyncScriptOptions): Promise<SyncResult>| Parameter | Type | Description |
|---|---|---|
options |
SyncScriptOptions |
The pull options |
| Property | Type | Required | Description |
|---|---|---|---|
scriptName |
string |
Yes | The name of the script record in ServiceNow |
scriptType |
string |
Yes | The script type key (e.g., "sys_script_include") |
filePath |
string |
Yes | The local file path to write the script to |
direction |
'push' | 'pull' |
No | Optional direction hint (not used by the method directly) |
Promise<SyncResult> containing:
scriptName: The script namescriptType: The script typefilePath: The file pathdirection:'pull'success: Whether the operation succeededsysId: The sys_id of the record (on success)message: A human-readable status messageerror: Error details (on failure)timestamp: ISO timestamp of the operation
const result = await scriptSync.pullScript({
scriptName: 'MyScriptInclude',
scriptType: 'sys_script_include',
filePath: './scripts/MyScriptInclude.sys_script_include.js'
});
if (result.success) {
console.log(`Pulled ${result.scriptName} (sys_id: ${result.sysId})`);
} else {
console.error(`Failed: ${result.error}`);
}Push a local file to ServiceNow by updating the script field on the matching record. Reads the file from the specified path, queries the table by name to find the record, then updates the script field.
async pushScript(options: SyncScriptOptions): Promise<SyncResult>| Parameter | Type | Description |
|---|---|---|
options |
SyncScriptOptions |
The push options |
Promise<SyncResult> containing:
scriptName: The script namescriptType: The script typefilePath: The file pathdirection:'push'success: Whether the operation succeededsysId: The sys_id of the updated record (on success)message: A human-readable status messageerror: Error details (on failure)timestamp: ISO timestamp of the operation
const result = await scriptSync.pushScript({
scriptName: 'MyScriptInclude',
scriptType: 'sys_script_include',
filePath: './scripts/MyScriptInclude.sys_script_include.js'
});
if (result.success) {
console.log(`Pushed ${result.scriptName} to ServiceNow`);
} else {
console.error(`Failed: ${result.error}`);
}Sync all scripts in a directory. Reads filenames, parses them using the {name}.{type}.js convention, and pushes each matching file to ServiceNow.
async syncAllScripts(options: SyncAllOptions): Promise<SyncAllResult>| Parameter | Type | Description |
|---|---|---|
options |
SyncAllOptions |
The sync-all options |
| Property | Type | Required | Description |
|---|---|---|---|
directory |
string |
Yes | Path to the directory containing script files |
scriptTypes |
string[] |
No | Filter to specific script type keys. Defaults to all supported types. |
Promise<SyncAllResult> containing:
directory: The directory that was syncedscriptTypes: The script types that were includedtotalFiles: Total number of valid files foundsynced: Number of files successfully syncedfailed: Number of files that failedscripts: Array of individualSyncResultentriestimestamp: ISO timestamp of the operation
const result = await scriptSync.syncAllScripts({
directory: './scripts',
scriptTypes: ['sys_script_include', 'sys_script']
});
console.log(`Total: ${result.totalFiles}`);
console.log(`Synced: ${result.synced}`);
console.log(`Failed: ${result.failed}`);
result.scripts.forEach(s => {
const status = s.success ? 'OK' : 'FAIL';
console.log(` [${status}] ${s.scriptName} (${s.scriptType})`);
});Parse a filename in the format {name}.{type}.js to extract the script name and type. Valid types are keys of the SCRIPT_TYPES map.
static parseFileName(fileName: string): ParsedFileName| Parameter | Type | Description |
|---|---|---|
fileName |
string |
The filename to parse |
ParsedFileName containing:
isValid: Whether the filename matched the expected patternscriptName: The extracted script name (when valid)scriptType: The extracted script type key (when valid)
const parsed = ScriptSync.parseFileName('MyUtils.sys_script_include.js');
if (parsed.isValid) {
console.log(`Name: ${parsed.scriptName}`); // "MyUtils"
console.log(`Type: ${parsed.scriptType}`); // "sys_script_include"
}
const invalid = ScriptSync.parseFileName('readme.txt');
console.log(invalid.isValid); // falseGenerate a filename in the format {name}.{type}.js.
static generateFileName(scriptName: string, scriptType: string): string| Parameter | Type | Description |
|---|---|---|
scriptName |
string |
The name of the script |
scriptType |
string |
The script type key |
string - The generated filename.
const fileName = ScriptSync.generateFileName('MyUtils', 'sys_script_include');
console.log(fileName); // "MyUtils.sys_script_include.js"interface ScriptTypeConfig {
table: string;
label: string;
nameField: string;
scriptField: string;
extension: string;
}const SCRIPT_TYPES: Record<string, ScriptTypeConfig> = {
sys_script_include: { table: 'sys_script_include', label: 'Script Include', nameField: 'name', scriptField: 'script', extension: '.js' },
sys_script: { table: 'sys_script', label: 'Business Rule', nameField: 'name', scriptField: 'script', extension: '.js' },
sys_ui_script: { table: 'sys_ui_script', label: 'UI Script', nameField: 'name', scriptField: 'script', extension: '.js' },
sys_ui_action: { table: 'sys_ui_action', label: 'UI Action', nameField: 'name', scriptField: 'script', extension: '.js' },
sys_script_client: { table: 'sys_script_client', label: 'Client Script', nameField: 'name', scriptField: 'script', extension: '.js' },
};interface SyncScriptOptions {
scriptName: string;
scriptType: string;
filePath: string;
direction?: 'push' | 'pull';
}interface SyncResult {
scriptName: string;
scriptType: string;
filePath: string;
direction: 'push' | 'pull';
success: boolean;
sysId?: string;
message: string;
error?: string;
timestamp: string;
}interface SyncAllOptions {
directory: string;
scriptTypes?: string[];
}interface SyncAllResult {
directory: string;
scriptTypes: string[];
totalFiles: number;
synced: number;
failed: number;
scripts: SyncResult[];
timestamp: string;
}interface ParsedFileName {
isValid: boolean;
scriptName?: string;
scriptType?: string;
}interface ScriptRecord {
sys_id: string;
name: string;
script: string;
[key: string]: unknown;
}interface ScriptRecordResponse {
result: ScriptRecord;
}interface ScriptRecordListResponse {
result: ScriptRecord[];
}async function pullScriptIncludes(scriptNames: string[]) {
const scriptSync = new ScriptSync(instance);
const outputDir = './scripts';
for (const name of scriptNames) {
const fileName = ScriptSync.generateFileName(name, 'sys_script_include');
const result = await scriptSync.pullScript({
scriptName: name,
scriptType: 'sys_script_include',
filePath: `${outputDir}/${fileName}`
});
if (result.success) {
console.log(`Pulled: ${result.scriptName}`);
} else {
console.error(`Failed to pull ${name}: ${result.error}`);
}
}
}async function pushAndReport() {
const scriptSync = new ScriptSync(instance);
const result = await scriptSync.syncAllScripts({
directory: './scripts'
});
console.log('\n=== Sync Report ===');
console.log(`Directory: ${result.directory}`);
console.log(`Total files: ${result.totalFiles}`);
console.log(`Synced: ${result.synced}`);
console.log(`Failed: ${result.failed}`);
console.log(`Timestamp: ${result.timestamp}`);
if (result.failed > 0) {
console.log('\nFailed scripts:');
result.scripts
.filter(s => !s.success)
.forEach(s => console.log(` - ${s.scriptName}: ${s.error}`));
}
}async function syncBusinessRulesOnly() {
const scriptSync = new ScriptSync(instance);
const result = await scriptSync.syncAllScripts({
directory: './scripts',
scriptTypes: ['sys_script']
});
console.log(`Synced ${result.synced} business rules`);
if (result.failed > 0) {
console.error(`${result.failed} business rules failed to sync`);
process.exit(1);
}
}async function roundTrip(scriptName: string) {
const scriptSync = new ScriptSync(instance);
const filePath = ScriptSync.generateFileName(scriptName, 'sys_script_include');
const localPath = `./scripts/${filePath}`;
// Pull the latest version
const pullResult = await scriptSync.pullScript({
scriptName,
scriptType: 'sys_script_include',
filePath: localPath
});
if (!pullResult.success) {
console.error(`Pull failed: ${pullResult.error}`);
return;
}
console.log(`Pulled ${scriptName} to ${localPath}`);
console.log('Edit the file locally, then run push...');
// After editing, push it back
const pushResult = await scriptSync.pushScript({
scriptName,
scriptType: 'sys_script_include',
filePath: localPath
});
if (pushResult.success) {
console.log(`Pushed ${scriptName} back to ServiceNow (sys_id: ${pushResult.sysId})`);
} else {
console.error(`Push failed: ${pushResult.error}`);
}
}- Follow the Naming Convention: Use the
{name}.{type}.jsformat for all script files so thatsyncAllScriptsandparseFileNamework correctly - Use
generateFileNamefor Consistency: Let the static helper produce filenames instead of manually constructing them - Pull Before Editing: Always pull the latest version from ServiceNow before making local changes to avoid overwriting others' work
- Filter Script Types: Use the
scriptTypesoption insyncAllScriptsto limit operations to the types you are working with - Check
SyncResult.success: Every sync operation returns a result object with asuccessflag; always check it rather than assuming the call succeeded - Handle Missing Records Gracefully: Both
pullScriptandpushScriptreturn a failure result (not an exception) when the script record is not found in ServiceNow